feat: add basic member support & squash migrations

This commit is contained in:
moanos [he/him] 2024-03-23 22:20:31 +01:00
parent 200338d44a
commit b03827fdaf
11 changed files with 183 additions and 155 deletions

View File

@ -1,7 +1,8 @@
# Generated by Django 5.0.3 on 2024-03-19 16:31 # Generated by Django 5.0.3 on 2024-03-23 10:04
import datetime import datetime
import django.db.models.deletion import django.db.models.deletion
import uuid
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -15,6 +16,27 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('image', models.ImageField(upload_to='images')),
('alt_text', models.TextField(max_length=2000)),
],
),
migrations.CreateModel(
name='Language',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Enter a natural languages name (e.g. English, French, Japanese etc.).', max_length=200, unique=True)),
('languagecode', models.CharField(help_text='Enter the language code for this language. For further information see http://www.i18nguy.com/unicode/language-identifiers.html', max_length=10, verbose_name='Language code')),
],
options={
'verbose_name': 'Language',
'verbose_name_plural': 'Languages',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Location', name='Location',
fields=[ fields=[
@ -36,6 +58,14 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Markdown content', 'verbose_name_plural': 'Markdown content',
}, },
), ),
migrations.CreateModel(
name='Rule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('rule_text', models.TextField()),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Species', name='Species',
fields=[ fields=[
@ -48,13 +78,51 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Image', name='AdoptionNotice',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)), ('created_at', models.DateField(default=datetime.datetime.now, verbose_name='Created at')),
('image', models.ImageField(upload_to='images')), ('searching_since', models.DateField(verbose_name='Searching for a home since')),
('alt_text', models.TextField(max_length=2000)), ('name', models.CharField(max_length=200)),
('uploaded_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('description', models.TextField(blank=True, null=True, verbose_name='Description')),
('further_information', models.URLField(blank=True, null=True, verbose_name='Link to further information')),
('group_only', models.BooleanField(default=False, verbose_name='Only group adoption')),
('photos', models.ManyToManyField(blank=True, to='fellchensammlung.image')),
],
),
migrations.CreateModel(
name='Member',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('trust_level', models.CharField(choices=[('admin', 'Administrator*in'), ('Moderator', 'Moderator*in'), ('Koordinator*in', 'Koordinator*in'), ('Mitglied', 'Mitglied')], default='Mitglied', max_length=100)),
('preferred_language', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.language', verbose_name='Preferred language')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Member',
'verbose_name_plural': 'Members',
},
),
migrations.CreateModel(
name='Report',
fields=[
('id', models.UUIDField(default=uuid.uuid4, help_text='ID dieses reports', primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('action taken', 'Action was taken'), ('no action taken', 'No action was taken'), ('waiting', 'Waiting for moderator action')], max_length=30)),
('comment', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice')),
('reported_broken_rules', models.ManyToManyField(blank=True, to='fellchensammlung.rule')),
],
),
migrations.CreateModel(
name='ModerationAction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action', models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('comment', 'Comment was added'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30)),
('created_at', models.DateTimeField(auto_now_add=True)),
('public_comment', models.TextField(blank=True)),
('private_comment', models.TextField(blank=True)),
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -70,19 +138,10 @@ class Migration(migrations.Migration):
('location', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.location')), ('location', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.location')),
], ],
), ),
migrations.CreateModel( migrations.AddField(
name='AdoptionNotice', model_name='adoptionnotice',
fields=[ name='organization',
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='fellchensammlung.rescueorganization', verbose_name='Organization'),
('created_at', models.DateField(default=datetime.datetime.now, verbose_name='Created at')),
('searching_since', models.DateField(verbose_name='Searching for a home since')),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True, null=True, verbose_name='Description')),
('further_information', models.URLField(blank=True, null=True, verbose_name='Link to further information')),
('group_only', models.BooleanField(default=False, verbose_name='Only group adoption')),
('photos', models.ManyToManyField(blank=True, to='fellchensammlung.image')),
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='fellchensammlung.rescueorganization', verbose_name='Organization')),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Animal', name='Animal',
@ -91,7 +150,7 @@ class Migration(migrations.Migration):
('date_of_birth', models.DateField(verbose_name='Date of birth')), ('date_of_birth', models.DateField(verbose_name='Date of birth')),
('name', models.CharField(max_length=200)), ('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True, null=True, verbose_name='Description')), ('description', models.TextField(blank=True, null=True, verbose_name='Description')),
('sex', models.CharField(choices=[('M_N', 'male_neutered'), ('M', 'male'), ('F_N', 'female_neutered'), ('F', 'female')], max_length=20)), ('sex', models.CharField(choices=[('M_N', 'neutered male'), ('M', 'male'), ('F_N', 'neutered female'), ('F', 'female')], max_length=20)),
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice')), ('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice')),
('photos', models.ManyToManyField(blank=True, to='fellchensammlung.image')), ('photos', models.ManyToManyField(blank=True, to='fellchensammlung.image')),
('species', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.species')), ('species', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.species')),

View File

@ -1,21 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-20 09:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Rule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('rule_text', models.TextField()),
],
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-20 10:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0002_rule'),
]
operations = [
migrations.RemoveField(
model_name='image',
name='uploaded_by',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-20 15:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0003_remove_image_uploaded_by'),
]
operations = [
migrations.AlterField(
model_name='animal',
name='sex',
field=models.CharField(choices=[('M_N', 'neutered male'), ('M', 'male'), ('F_N', 'neutered female'), ('F', 'female')], max_length=20),
),
]

View File

@ -1,37 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-22 09:20
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0004_alter_animal_sex'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.UUIDField(default=uuid.uuid4, help_text='ID dieses reports', primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('action taken', 'Action was taken'), ('no action taken', 'No action was taken'), ('waiting', 'Waiting for moderator action')], max_length=30)),
('comment', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice')),
('reported_broken_rules', models.ManyToManyField(blank=True, to='fellchensammlung.rule')),
],
),
migrations.CreateModel(
name='ModerationAction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action', models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30)),
('created_at', models.DateTimeField(auto_now_add=True)),
('public_comment', models.TextField()),
('private_comment', models.TextField()),
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report')),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-22 11:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0005_report_moderationaction'),
]
operations = [
migrations.AlterField(
model_name='moderationaction',
name='action',
field=models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('comment', 'Comment was added'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-22 11:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0006_alter_moderationaction_action'),
]
operations = [
migrations.AlterField(
model_name='moderationaction',
name='private_comment',
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name='moderationaction',
name='public_comment',
field=models.TextField(blank=True),
),
]

View File

@ -5,6 +5,8 @@ from django.urls import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from datetime import datetime from datetime import datetime
from django.dispatch import receiver
from django.db.models.signals import post_save
from fellchensammlung.tools import misc from fellchensammlung.tools import misc
@ -250,3 +252,75 @@ class ModerationAction(models.Model):
def __str__(self): def __str__(self):
return f"[{self.action}]: {self.public_comment}" return f"[{self.action}]: {self.public_comment}"
class Language(models.Model):
"""Model representing a Language (e.g. English, French, Japanese, etc.)"""
name = models.CharField(max_length=200,
help_text=_("Enter a natural languages name (e.g. English, French, Japanese etc.)."),
unique=True)
languagecode = models.CharField(max_length=10,
# Translators: This helptext includes an URL
help_text=_(
"Enter the language code for this language. For further information see http://www.i18nguy.com/unicode/language-identifiers.html"),
verbose_name=_('Language code'))
def __str__(self):
"""String for representing the Model object (in Admin site etc.)"""
return self.name
class Meta:
verbose_name = _('Language')
verbose_name_plural = _('Languages')
"""
Membership
"""
class Member(models.Model):
"""
Model that holds a user's profile, including the django user model
It is created upon creation of a new django user (see add_member)
The trust levels act as permission system and can be displayed as a badge for the user
"""
# Admins can perform all actions and have the highest trust associated with them
# Moderators can make moderation decisions regarding the deletion of content
# Coordinators can create adoption notices without them being checked
# Members can create adoption notices that must be activated
ADMIN = "admin"
MODERATOR = "Moderator"
COORDINATOR = "Koordinator*in"
MEMBER = "Mitglied"
TRUES_LEVEL = {
ADMIN: "Administrator*in",
MODERATOR: "Moderator*in",
COORDINATOR: "Koordinator*in",
MEMBER: "Mitglied",
}
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('User'))
preferred_language = models.ForeignKey(Language, on_delete=models.PROTECT, null=True, blank=True,
verbose_name=_('Preferred language'))
trust_level = models.CharField(choices=TRUES_LEVEL, max_length=100, default=MEMBER)
class Meta:
verbose_name = _('Member')
verbose_name_plural = _('Members')
@receiver(post_save, sender=User)
def add_member(sender, instance, created, raw, using, **kwargs):
if len(Member.objects.filter(user=instance)) != 1:
Member.objects.create(user=instance)
def __str__(self):
return str(self.user)
def get_absolute_url(self):
return reverse("member-detail", args=[str(self.user.id)])

View File

@ -0,0 +1,16 @@
{% extends "library/base_generic.html" %}
{% load i18n %}
{% block content %}
<h1>{{ member.user.get_full_name }}</h1>
<p><strong>{% translate "Username" %}:</strong> {{ member.user.username }}</p>
<p><strong>{% translate "E-Mail" %}:</strong> {{ member.user.email }}</p>
{% if member.preferred_language %}
<p><strong>{% translate "Language" %}:</strong> {{ member.preferred_language }}</p>
{% else %}
<p>{% translate "No preferred language set." %}</p>
{% endif %}
{% endblock %}

View File

@ -27,4 +27,10 @@ urlpatterns = [
path("melden/<int:adoption_notice_id>/", views.report_adoption, name="report-adoption-notice"), path("melden/<int:adoption_notice_id>/", views.report_adoption, name="report-adoption-notice"),
path("meldung/<uuid:report_id>/", views.report_detail, name="report-detail"), path("meldung/<uuid:report_id>/", views.report_detail, name="report-detail"),
path("meldung/<uuid:report_id>/sucess", views.report_detail_success, name="report-detail-success"), path("meldung/<uuid:report_id>/sucess", views.report_detail_success, name="report-detail-success"),
###########
## USERS ##
###########
path("user/<int:user_id>/", views.member_detail, name="user-detail"),
] ]

View File

@ -3,7 +3,8 @@ from django.http import HttpResponse
from django.urls import reverse from django.urls import reverse
import markdown import markdown
from fellchensammlung.models import AdoptionNotice, MarkdownContent, Animal, Rule, Image, Report, ModerationAction from fellchensammlung.models import AdoptionNotice, MarkdownContent, Animal, Rule, Image, Report, ModerationAction, \
Member
from .forms import AdoptionNoticeForm, AnimalForm, ImageForm, ReportForm from .forms import AdoptionNoticeForm, AnimalForm, ImageForm, ReportForm
@ -118,3 +119,9 @@ def report_detail_success(request, report_id):
Calls the report detail view with form_complete set to true, so success message shows Calls the report detail view with form_complete set to true, so success message shows
""" """
return report_detail(request, report_id, form_complete=True) return report_detail(request, report_id, form_complete=True)
def member_detail(request, user):
member = Member.objects.get(user=user)
context = {"member": member}
return render(request, 'fellchensammlung/detail-member.html', context=context)