From 4dbde11a0be5d23e5187b5ce6c197df6209dca99 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sun, 24 Jan 2021 15:43:51 +0100 Subject: [PATCH] Working list of messages --- poetry.lock | 46 ++++++++++- pyproject.toml | 2 + src/picture_display/settings.py | 28 +++++-- src/pictures/admin.py | 33 +++++++- .../migrations/0002_contact_media_message.py | 80 +++++++++++++++++++ .../migrations/0003_message_received_at.py | 19 +++++ .../migrations/0004_auto_20210124_1407.py | 32 ++++++++ src/pictures/models.py | 44 ++++++++++ src/pictures/static/pictures/style.css | 48 +++++++++++ src/pictures/templates/pictures/base.html | 20 +++++ .../templates/pictures/messages-list.html | 22 +++++ src/pictures/urls.py | 8 +- src/pictures/views.py | 9 ++- 13 files changed, 379 insertions(+), 12 deletions(-) create mode 100644 src/pictures/migrations/0002_contact_media_message.py create mode 100644 src/pictures/migrations/0003_message_received_at.py create mode 100644 src/pictures/migrations/0004_auto_20210124_1407.py create mode 100644 src/pictures/static/pictures/style.css create mode 100644 src/pictures/templates/pictures/base.html create mode 100644 src/pictures/templates/pictures/messages-list.html diff --git a/poetry.lock b/poetry.lock index fa17d31..7c4e1fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,6 +80,30 @@ sqlparse = ">=0.2.2" argon2 = ["argon2-cffi (>=16.1.0)"] 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]] name = "filelock" version = "3.0.12" @@ -141,6 +165,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] 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]] name = "pluggy" version = "0.13.1" @@ -286,7 +318,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "bb2539651a82ba0577d1d9f1a70d2777f12479cffd8ed4f1d9fd8cdffdbbbef1" +content-hash = "ebea8c626e42840de0ccf13d5cdb190d18b04028b1115d0ff639eb37dd93cfb6" [metadata.files] appdirs = [ @@ -321,6 +353,14 @@ django = [ {file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"}, {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 = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {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.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 = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, diff --git a/pyproject.toml b/pyproject.toml index 8eb1422..c033f96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ authors = ["Gabriel Augendre "] [tool.poetry.dependencies] python = "^3.8" Django = "^3.1.5" +django-phonenumber-field = {extras = ["phonenumbers"], version = "^5.0.0"} +django-cleanup = "^5.1.0" [tool.poetry.dev-dependencies] pytest = "^6.2" diff --git a/src/picture_display/settings.py b/src/picture_display/settings.py index a925651..69724e3 100644 --- a/src/picture_display/settings.py +++ b/src/picture_display/settings.py @@ -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! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = [ + "*", +] # Application definition -CUSTOM_APPS = ["pictures"] +CUSTOM_APPS = [ + "pictures", +] -INSTALLED_APPS = [ +DJANGO_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", -] + CUSTOM_APPS +] + +EXTERNAL_APPS = [ + "phonenumber_field", + "django_cleanup.apps.CleanupConfig", +] + +INSTALLED_APPS = DJANGO_APPS + CUSTOM_APPS + EXTERNAL_APPS MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", @@ -112,9 +123,9 @@ AUTH_PASSWORD_VALIDATORS = [ # USE_TZ = True LANGUAGE_CODE = "fr-fr" -TIME_ZONE = None -USE_I18N = False -USE_L10N = False +TIME_ZONE = "Europe/Paris" +USE_I18N = True +USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) @@ -123,3 +134,6 @@ USE_TZ = False STATIC_URL = "/static/" AUTH_USER_MODEL = "pictures.User" + +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "media" diff --git a/src/pictures/admin.py b/src/pictures/admin.py index 6bacd7b..37482ac 100644 --- a/src/pictures/admin.py +++ b/src/pictures/admin.py @@ -1,6 +1,37 @@ from django.contrib import admin +from django.contrib.admin import register 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) + + +@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", + ] diff --git a/src/pictures/migrations/0002_contact_media_message.py b/src/pictures/migrations/0002_contact_media_message.py new file mode 100644 index 0000000..d7e96eb --- /dev/null +++ b/src/pictures/migrations/0002_contact_media_message.py @@ -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", + ), + ), + ], + ), + ] diff --git a/src/pictures/migrations/0003_message_received_at.py b/src/pictures/migrations/0003_message_received_at.py new file mode 100644 index 0000000..7afe246 --- /dev/null +++ b/src/pictures/migrations/0003_message_received_at.py @@ -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), + ), + ] diff --git a/src/pictures/migrations/0004_auto_20210124_1407.py b/src/pictures/migrations/0004_auto_20210124_1407.py new file mode 100644 index 0000000..90c66a4 --- /dev/null +++ b/src/pictures/migrations/0004_auto_20210124_1407.py @@ -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", + ), + ), + ] diff --git a/src/pictures/models.py b/src/pictures/models.py index 7e21811..ae5f1d6 100644 --- a/src/pictures/models.py +++ b/src/pictures/models.py @@ -1,6 +1,50 @@ +import datetime + from django.contrib.auth.models import AbstractUser 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): 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 diff --git a/src/pictures/static/pictures/style.css b/src/pictures/static/pictures/style.css new file mode 100644 index 0000000..649df61 --- /dev/null +++ b/src/pictures/static/pictures/style.css @@ -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; +} diff --git a/src/pictures/templates/pictures/base.html b/src/pictures/templates/pictures/base.html new file mode 100644 index 0000000..376618d --- /dev/null +++ b/src/pictures/templates/pictures/base.html @@ -0,0 +1,20 @@ +{% load static %} + + + + + + Picture Display + + + + + +
+ {% block content %} + {% endblock %} +
+ + diff --git a/src/pictures/templates/pictures/messages-list.html b/src/pictures/templates/pictures/messages-list.html new file mode 100644 index 0000000..d2a8526 --- /dev/null +++ b/src/pictures/templates/pictures/messages-list.html @@ -0,0 +1,22 @@ +{% extends "pictures/base.html" %} + +{% block content %} + {% for message in messages %} +
+
+ {% if message.media_files.first %} + + {% else %} +
+ {% endif %} +
+
+ +
{{ message.content | linebreaks }}
+
+
+ {% endfor %} +{% endblock %} diff --git a/src/pictures/urls.py b/src/pictures/urls.py index b9cad47..1e306fd 100644 --- a/src/pictures/urls.py +++ b/src/pictures/urls.py @@ -1,5 +1,9 @@ +from django.conf import settings +from django.conf.urls.static import static from django.urls import path +from pictures.views import MessageListView + urlpatterns = [ - # path("", PicturesView.as_view(), name="pictures-list"), -] + path("", MessageListView.as_view(), name="messages-list"), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/pictures/views.py b/src/pictures/views.py index 91ea44a..dd61f65 100644 --- a/src/pictures/views.py +++ b/src/pictures/views.py @@ -1,3 +1,10 @@ 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"