Working list of messages

This commit is contained in:
Gabriel Augendre 2021-01-24 15:43:51 +01:00
parent 17030fb850
commit 4dbde11a0b
13 changed files with 379 additions and 12 deletions

46
poetry.lock generated
View file

@ -80,6 +80,30 @@ sqlparse = ">=0.2.2"
argon2 = ["argon2-cffi (>=16.1.0)"] argon2 = ["argon2-cffi (>=16.1.0)"]
bcrypt = ["bcrypt"] bcrypt = ["bcrypt"]
[[package]]
name = "django-cleanup"
version = "5.1.0"
description = "Deletes old files."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "django-phonenumber-field"
version = "5.0.0"
description = "An international phone number field for django models."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
Django = ">=2.2"
phonenumbers = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumbers\""}
[package.extras]
phonenumbers = ["phonenumbers (>=7.0.2)"]
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.0.12" version = "3.0.12"
@ -141,6 +165,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
[[package]]
name = "phonenumbers"
version = "8.12.16"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "0.13.1" version = "0.13.1"
@ -286,7 +318,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "bb2539651a82ba0577d1d9f1a70d2777f12479cffd8ed4f1d9fd8cdffdbbbef1" content-hash = "ebea8c626e42840de0ccf13d5cdb190d18b04028b1115d0ff639eb37dd93cfb6"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
@ -321,6 +353,14 @@ django = [
{file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"}, {file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"},
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"}, {file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
] ]
django-cleanup = [
{file = "django-cleanup-5.1.0.tar.gz", hash = "sha256:8976aec12a22913afb3d1fcb541b1aedde2f5ec243e4260c5ff78bb6aa75a089"},
{file = "django_cleanup-5.1.0-py2.py3-none-any.whl", hash = "sha256:71e098c7d9ac3f3da40b95cff9c0bc51218d40ef419261232f46ba3141c50acc"},
]
django-phonenumber-field = [
{file = "django-phonenumber-field-5.0.0.tar.gz", hash = "sha256:1eb7af3a108744665f7c3939d38aa15b3728c57d13d45d656b0a2aa11e8cdc3c"},
{file = "django_phonenumber_field-5.0.0-py3-none-any.whl", hash = "sha256:adb46905cc4ecb19d8494424e1c4352f24946bb472340a2a17257d44bf8228e6"},
]
filelock = [ filelock = [
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
@ -345,6 +385,10 @@ packaging = [
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
] ]
phonenumbers = [
{file = "phonenumbers-8.12.16-py2.py3-none-any.whl", hash = "sha256:56ad29025b8f885945506350b06d77afbc506c5463141d77a5df767280a7ee0b"},
{file = "phonenumbers-8.12.16.tar.gz", hash = "sha256:a820ab08c980ef24a2d2a1ead4f8d7016fdf008e484d1aecf7ff0b32cc475e16"},
]
pluggy = [ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},

View file

@ -7,6 +7,8 @@ authors = ["Gabriel Augendre <gabriel@augendre.info>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
Django = "^3.1.5" Django = "^3.1.5"
django-phonenumber-field = {extras = ["phonenumbers"], version = "^5.0.0"}
django-cleanup = "^5.1.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^6.2" pytest = "^6.2"

View file

@ -25,21 +25,32 @@ SECRET_KEY = "-kza5t&!y!4tt9t0ha+ggadil@bz9wca2f$!=@@efx*&-!4+@t"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = [
"*",
]
# Application definition # Application definition
CUSTOM_APPS = ["pictures"] CUSTOM_APPS = [
"pictures",
]
INSTALLED_APPS = [ DJANGO_APPS = [
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
] + CUSTOM_APPS ]
EXTERNAL_APPS = [
"phonenumber_field",
"django_cleanup.apps.CleanupConfig",
]
INSTALLED_APPS = DJANGO_APPS + CUSTOM_APPS + EXTERNAL_APPS
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
@ -112,9 +123,9 @@ AUTH_PASSWORD_VALIDATORS = [
# USE_TZ = True # USE_TZ = True
LANGUAGE_CODE = "fr-fr" LANGUAGE_CODE = "fr-fr"
TIME_ZONE = None TIME_ZONE = "Europe/Paris"
USE_I18N = False USE_I18N = True
USE_L10N = False USE_L10N = True
USE_TZ = False USE_TZ = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
@ -123,3 +134,6 @@ USE_TZ = False
STATIC_URL = "/static/" STATIC_URL = "/static/"
AUTH_USER_MODEL = "pictures.User" AUTH_USER_MODEL = "pictures.User"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

View file

@ -1,6 +1,37 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import register
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from pictures.models import User from pictures.models import Contact, Media, Message, User
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
@register(Contact)
class ContactAdmin(admin.ModelAdmin):
list_display = [
"display_name",
"phone_number",
]
class MediaInline(admin.TabularInline):
model = Media
@register(Message)
class MessageAdmin(admin.ModelAdmin):
list_display = [
"sender",
"content",
]
inlines = [
MediaInline,
]
@register(Media)
class MediaAdmin(admin.ModelAdmin):
list_display = [
"sender",
]

View file

@ -0,0 +1,80 @@
# Generated by Django 3.1.5 on 2021-01-24 13:07
import django.db.models.deletion
import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pictures", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Contact",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"phone_number",
phonenumber_field.modelfields.PhoneNumberField(
max_length=128, region=None
),
),
("display_name", models.CharField(max_length=250)),
],
),
migrations.CreateModel(
name="Message",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("content", models.TextField(blank=True)),
(
"sender",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="pictures.contact",
),
),
],
),
migrations.CreateModel(
name="Media",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("file", models.FileField(upload_to="")),
(
"message",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="pictures.message",
),
),
],
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-01-24 13:56
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pictures", "0002_contact_media_message"),
]
operations = [
migrations.AddField(
model_name="message",
name="received_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View file

@ -0,0 +1,32 @@
# Generated by Django 3.1.5 on 2021-01-24 14:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pictures", "0003_message_received_at"),
]
operations = [
migrations.AlterField(
model_name="media",
name="message",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="media_files",
to="pictures.message",
),
),
migrations.AlterField(
model_name="message",
name="sender",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="messages",
to="pictures.contact",
),
),
]

View file

@ -1,6 +1,50 @@
import datetime
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.text import Truncator
from phonenumber_field.modelfields import PhoneNumberField
class User(AbstractUser): class User(AbstractUser):
pass pass
class Contact(models.Model):
phone_number = PhoneNumberField()
display_name = models.CharField(max_length=250)
def __str__(self):
return self.display_name
class Message(models.Model):
sender = models.ForeignKey(
Contact,
on_delete=models.PROTECT,
related_name="messages",
)
content = models.TextField(blank=True)
received_at = models.DateTimeField(default=timezone.now)
def __str__(self):
truncator = Truncator(self.content)
truncated_content = truncator.words(15)
return f"De {self.sender}: {truncated_content}"
class Media(models.Model):
message = models.ForeignKey(
Message,
on_delete=models.CASCADE,
related_name="media_files",
)
file = models.FileField()
def __str__(self):
return f"Envoyé avec '{self.message}'"
@property
def sender(self):
return self.message.sender

View file

@ -0,0 +1,48 @@
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
* {
margin: 0;
padding: 0;
}
main {
margin: 1em auto;
max-width: 750px;
}
.message {
display: flex;
flex-direction: row;
margin-bottom: 2em;
}
.message .picture-preview {
flex-grow: 0;
}
.picture-preview > * {
height: 200px;
width: 200px;
object-fit: cover;
background-color: lightgray;
border-radius: 5px;
}
.message .content-block {
flex-grow: 1;
margin-left: 1em;
}
.message .content-block .metadata .name {
font-size: 120%;
}
.message .content-block .metadata .date {
color: #626262;
}
.message .content-block .text {
margin-top: 1em;
}

View file

@ -0,0 +1,20 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Picture Display</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static "pictures/style.css" %}">
</head>
<body>
<nav>
{% block buttons %}{% endblock %}
</nav>
<main>
{% block content %}
{% endblock %}
</main>
</body>
</html>

View file

@ -0,0 +1,22 @@
{% extends "pictures/base.html" %}
{% block content %}
{% for message in messages %}
<div class="message">
<div class="picture-preview">
{% if message.media_files.first %}
<img src="{{ message.media_files.first.file.url }}" alt="">
{% else %}
<div class="filler"></div>
{% endif %}
</div>
<div class="content-block">
<div class="metadata">
<div class="name">De: {{ message.sender.display_name }}</div>
<div class="date">{{ message.received_at }}</div>
</div>
<div class="text">{{ message.content | linebreaks }}</div>
</div>
</div>
{% endfor %}
{% endblock %}

View file

@ -1,5 +1,9 @@
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path from django.urls import path
from pictures.views import MessageListView
urlpatterns = [ urlpatterns = [
# path("", PicturesView.as_view(), name="pictures-list"), path("", MessageListView.as_view(), name="messages-list"),
] ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -1,3 +1,10 @@
from django.shortcuts import render from django.shortcuts import render
from django.views import generic
# Create your views here. from pictures.models import Message
class MessageListView(generic.ListView):
model = Message
template_name = "pictures/messages-list.html"
context_object_name = "messages"