Working list of messages
This commit is contained in:
parent
17030fb850
commit
4dbde11a0b
13 changed files with 379 additions and 12 deletions
46
poetry.lock
generated
46
poetry.lock
generated
|
@ -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"},
|
||||
|
|
|
@ -7,6 +7,8 @@ authors = ["Gabriel Augendre <gabriel@augendre.info>"]
|
|||
[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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
|
80
src/pictures/migrations/0002_contact_media_message.py
Normal file
80
src/pictures/migrations/0002_contact_media_message.py
Normal 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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
19
src/pictures/migrations/0003_message_received_at.py
Normal file
19
src/pictures/migrations/0003_message_received_at.py
Normal 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),
|
||||
),
|
||||
]
|
32
src/pictures/migrations/0004_auto_20210124_1407.py
Normal file
32
src/pictures/migrations/0004_auto_20210124_1407.py
Normal 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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
48
src/pictures/static/pictures/style.css
Normal file
48
src/pictures/static/pictures/style.css
Normal 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;
|
||||
}
|
20
src/pictures/templates/pictures/base.html
Normal file
20
src/pictures/templates/pictures/base.html
Normal 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>
|
22
src/pictures/templates/pictures/messages-list.html
Normal file
22
src/pictures/templates/pictures/messages-list.html
Normal 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 %}
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue