diff --git a/fet2020/members/admin.py b/fet2020/members/admin.py index 502b1c34..c417cf50 100644 --- a/fet2020/members/admin.py +++ b/fet2020/members/admin.py @@ -1,29 +1,12 @@ from django.contrib import admin -from .models import Member, Job, JobMember -from .forms import MyMemberForm, MyJobForm - from django.utils.translation import gettext as _ -class ActiveJobFilter(admin.SimpleListFilter): - title = _('Aktiv im Job') +from .models import Member, Job, JobMember, JobGroup +from .forms import MyMemberForm, MyJobForm, MyJobGroupForm - parameter_name = 'is_active' - - def lookups(self, request, model_admin): - return ( - ('yes', _('Yes')), - ('no', _('No')), - ) - - def queryset(self, request, queryset): - if self.value() == 'yes': - return queryset.filter(job_end__isnull=True) - elif self.value() == 'no': - return queryset class MemberRoleFilter(admin.SimpleListFilter): title = _('Rolle') - parameter_name = 'role' def lookups(self, request, model_admin): @@ -39,19 +22,36 @@ class MemberRoleFilter(admin.SimpleListFilter): return queryset.filter(role='P') class JobMemberInline(admin.TabularInline): - list_filter = [ActiveJobFilter] model = JobMember extra = 0 - #def get_queryset(self, request): - # qs = super().get_queryset(request) - # return qs.filter(job_end__isnull=False) +class JobOverviewInline(JobMemberInline): + verbose_name = "Tätigkeit" + verbose_name_plural = "Tätigkeitsübersicht" + +class ActiveMemberInline(JobMemberInline): + verbose_name = "Mitglied" + verbose_name_plural = "Aktive Mitglieder Liste" + + def get_queryset(self, request): + return JobMember.active_member.all() + +class InactiveMemberInline(JobMemberInline): + verbose_name = "Mitglied" + verbose_name_plural = "Inaktive Mitglieder Liste" + + def get_queryset(self, request): + return JobMember.inactive_member.all() + +class JobInline(admin.TabularInline): + model = Job + extra = 0 class MyMemberAdmin(admin.ModelAdmin): form = MyMemberForm model = Member list_display = ['nickname', 'firstname', 'surname', 'mailaccount', 'role'] - inlines = (JobMemberInline,) + inlines = (JobOverviewInline,) search_fields = ['firstname', 'surname','nickname','mailaccount'] list_filter = [MemberRoleFilter] @@ -66,7 +66,7 @@ class MyJobAdmin(admin.ModelAdmin): form = MyJobForm model = Job list_display = ['name'] - inlines = (JobMemberInline,) + inlines = (ActiveMemberInline, InactiveMemberInline) search_fields = ['name'] @@ -74,4 +74,18 @@ class MyJobAdmin(admin.ModelAdmin): obj.author = request.user super().save_model(request, obj, form, change) -admin.site.register(Job, MyJobAdmin) \ No newline at end of file +admin.site.register(Job, MyJobAdmin) + +class MyJobGroupAdmin(admin.ModelAdmin): + form = MyJobGroupForm + model = JobGroup + list_display = ['name'] + inlines = (JobInline, ) + + search_fields = ['name'] + + def save_model(self, request, obj, form, change): + obj.author = request.user + super().save_model(request, obj, form, change) + +admin.site.register(JobGroup, MyJobGroupAdmin) \ No newline at end of file diff --git a/fet2020/members/forms.py b/fet2020/members/forms.py index 24f729f8..c097cc63 100644 --- a/fet2020/members/forms.py +++ b/fet2020/members/forms.py @@ -1,7 +1,7 @@ from django import forms from ckeditor_uploader.widgets import CKEditorUploadingWidget -from .models import Member, Job +from .models import Member, Job, JobGroup class MyMemberForm(forms.ModelForm): @@ -25,6 +25,11 @@ class MyMemberForm(forms.ModelForm): class MyJobForm(forms.ModelForm): class Meta: model = Job - fields = ['name', 'description', 'image'] + fields = ['name', 'shortterm', 'slug', 'job_group', 'description', 'image'] - widgets = {'description': CKEditorUploadingWidget(config_name='default')} \ No newline at end of file + widgets = {'description': CKEditorUploadingWidget(config_name='default')} + +class MyJobGroupForm(forms.ModelForm): + class Meta: + model = JobGroup + fields = ['name', 'shortterm', 'is_pinned'] \ No newline at end of file diff --git a/fet2020/members/models.py b/fet2020/members/models.py index f326a6f3..ee1546db 100644 --- a/fet2020/members/models.py +++ b/fet2020/members/models.py @@ -1,10 +1,15 @@ from django.core.validators import RegexValidator from django.db import models +from django.db.models import Q +from django.utils import timezone +from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from rest_framework import serializers import uuid +from datetime import timedelta + class MemberManager(models.Manager): def get_queryset(self): @@ -14,6 +19,36 @@ class PensionManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(role='P') +class ActiveMemberManager(models.Manager): + ''' + return a list of active member, and members who are still working + ''' + def get_queryset(self): + date_today = timezone.now().date() + + return super().get_queryset().filter( + Q(member__role='A') & + ( + Q(job_end__gt=date_today) | + Q(job_end__isnull=True) + ) + ) + +class InactiveMemberManager(models.Manager): + ''' + return a list of inactive member + ''' + def get_queryset(self): + date_today = timezone.now().date() + + return super().get_queryset().filter( + Q(member__role='P') | + ( + Q(job_end__lt=date_today + timedelta(days=1)) & + Q(job_end__isnull=False) + ) + ) + class Member(models.Model): firstname = models.CharField(max_length=128) surname = models.CharField(max_length=128) @@ -31,8 +66,10 @@ class Member(models.Model): birthday = models.DateField(null=True, blank=True) - phone_error_msg =_(("Phone number must be entered in the format: " - "+999999999'. Up to 15 digits allowed.")) + phone_error_msg =_(( + "Phone number must be entered in the format: " + "+999999999'. Up to 15 digits allowed." + )) phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message=phone_error_msg) phone = models.CharField(validators=[phone_regex], max_length=17, blank=True) @@ -41,17 +78,15 @@ class Member(models.Model): date_modified = models.DateTimeField(auto_now=True) date_created = models.DateTimeField(auto_now_add=True) - has_jobs = models.ManyToManyField( - 'Job', - through='JobMember', - through_fields=('member', 'job') - ) - # Managers all_members = models.Manager() active_member = MemberManager() pension_member = PensionManager() + class Meta: + verbose_name = "Mitglied" + verbose_name_plural = "Mitglieder" + def __str__(self): return self.firstname + " " + self.surname @@ -68,24 +103,53 @@ class MemberSerializer(serializers.HyperlinkedModelSerializer): 'image' ] -class Job(models.Model): +class JobGroup(models.Model): name = models.CharField(max_length=128) - - description = models.TextField(null=True, blank=True) - image = models.ImageField(null=True, blank=True) - - has_members = models.ManyToManyField( - 'Member', - through='JobMember', - through_fields=('job', 'member') - ) + shortterm = models.CharField(max_length=128) + is_pinned = models.BooleanField(default=False) def __str__(self): return self.name +class Job(models.Model): + name = models.CharField(max_length=128) + shortterm = models.CharField(max_length=128) + + slug = models.SlugField(unique=True, null=True, blank=True) + + description = models.TextField(null=True, blank=True) + image = models.ImageField(null=True, blank=True) + + job_group = models.ForeignKey( + JobGroup, + on_delete=models.CASCADE, + verbose_name="Job Gruppe", + ) + + class Meta: + verbose_name = "Tätigkeit" + verbose_name_plural = "Tätigkeiten" + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.shortterm) + + super().save(*args, **kwargs) + + def __str__(self): + return self.shortterm + class JobMember(models.Model): - member = models.ForeignKey(Member, on_delete=models.CASCADE) - job = models.ForeignKey(Job, on_delete=models.CASCADE) + member = models.ForeignKey( + Member, + on_delete=models.CASCADE, + verbose_name="Mitglied", + ) + job = models.ForeignKey( + Job, + on_delete=models.CASCADE, + verbose_name="Tätigkeit", + ) job_start = models.DateField('Job Start') job_end = models.DateField('Job Ende', null=True, blank=True) @@ -100,7 +164,6 @@ class JobMember(models.Model): ] job_role = models.CharField(max_length=2, choices=__choices, default='M') - class Meta: - unique_together = [['member', 'job']] - - jobmember = models.Manager() \ No newline at end of file + jobmember = models.Manager() + active_member = ActiveMemberManager() + inactive_member = InactiveMemberManager() \ No newline at end of file diff --git a/fet2020/members/views.py b/fet2020/members/views.py index b90fac19..73fb47a6 100644 --- a/fet2020/members/views.py +++ b/fet2020/members/views.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from collections import deque -from .models import Member, JobMember, MemberSerializer +from .models import Member, Job, JobMember, JobGroup, MemberSerializer from rest_framework import viewsets from rest_framework import permissions @@ -12,12 +12,16 @@ from django_filters.rest_framework import DjangoFilterBackend def index(request): #members = deque(Member.all_members.all()) - members = deque(Member.all_members.prefetch_related('has_jobs')) - - #jobmember = deque(JobMember.jobmember.all()) + members = deque(Member.all_members.all()) + jobmembers = deque(JobMember.jobmember.all()) + jobs = deque(Job.objects.all()) + jobgroups = deque(JobGroup.objects.all()) context = { - "member": members + "members": members, + "jobmembers" : jobmembers, + "jobgroups" : jobgroups, + "jobs" : jobs, } return render(request, 'members/index.html', context) diff --git a/fet2020/posts/models.py b/fet2020/posts/models.py index 46a96eff..90a8a12d 100644 --- a/fet2020/posts/models.py +++ b/fet2020/posts/models.py @@ -33,7 +33,7 @@ class ArticleManager(models.Manager): regular fet meetings should not be contained in the news stream """ def get_queryset(self): - return super().get_queryset().filter(Q(news_type='E') | Q(news_type='N')) + return super().get_queryset().filter(Q(post_type='E') | Q(post_type='N')) class NewsManager(models.Manager): def get_queryset(self): @@ -45,11 +45,11 @@ class EventManager(models.Manager): regular fet meetings should not be contained in the news stream """ def get_queryset(self): - return super().get_queryset().filter(Q(is_event=True) & Q(news_type='E')) + return super().get_queryset().filter(Q(is_event=True) & Q(post_type='E')) class FetMeetingManager(models.Manager): def get_queryset(self): - return super().get_queryset().filter(Q(news_type='F')) + return super().get_queryset().filter(Q(post_type='F')) ########## @@ -100,7 +100,7 @@ class Post(models.Model): ('E', _('Event')), ('F', _('FetMeeting')) ] - news_type = models.CharField(max_length=1, choices=__choices, default='N', editable=False) + post_type = models.CharField(max_length=1, choices=__choices, editable=False) is_event = models.BooleanField(default=False) @@ -163,12 +163,12 @@ class Post(models.Model): "save the post with some defaults" if (self.id is None) and (not self.slug): self.slug = slugify(self.public_date.date()) + "-" + slugify(self.title) - + super().save(*args, **kwargs) self.tags.set(*re.findall(r'\#([\d\w-]+)', str(self.subtitle)), *re.findall(r'\#([\d\w-]+)', str(self.title))) - + def __str__(self): return "Post (%s, %s): %s " %(self.slug, self.public_date.strftime("%d.%m.%Y"), self.title) @@ -199,7 +199,8 @@ class News(Post): verbose_name_plural = "News" def save(self, *args, **kwargs): - self.news_type = 'N' + if not self.post_type: + self.post_type = 'N' super().save(*args, **kwargs) @@ -211,8 +212,9 @@ class Event(Post): def save(self, *args, **kwargs): self.is_event = True - if self.news_type == 'N': - self.news_type = 'E' + + if not self.post_type: + self.post_type = 'E' super().save(*args, **kwargs) @@ -240,8 +242,8 @@ class FetMeeting(Event): # self.protocol_key # self.agenda_key - if self.news_type == 'N': - self.news_type = 'F' + if not self.post_type: + self.post_type = 'F' if not self.event_place: self.event_place = "FET" diff --git a/fet2020/posts/views.py b/fet2020/posts/views.py index 7b114e18..6a8b5f9d 100644 --- a/fet2020/posts/views.py +++ b/fet2020/posts/views.py @@ -1,43 +1,36 @@ from django.shortcuts import render from django.http import HttpResponse, JsonResponse -from collections import deque +from django_filters.rest_framework import DjangoFilterBackend +from django.core.cache import cache +from django.utils.text import slugify +from django.utils import timezone -from .models import Post, PostSerializer from taggit.models import Tag from rest_framework import viewsets from rest_framework import permissions -from django_filters.rest_framework import DjangoFilterBackend -from django.core.cache import cache +from .models import Post, PostSerializer -from django.utils.text import slugify -from django.utils import timezone +from collections import deque -def get_next_dict(): - posts = Post.article_objects.order_by('-public_date').values('slug') - print(posts) - d = {} - print(d) - for k,v in enumerate(posts): - if k == len(posts) - 1: - break - d[v['slug']] = posts[k+1]['slug'] - print(d) - return d +################## +# RENDERED VIEWS # +################## -# Create your views here. def index(request): posts = deque(Post.objects.order_by('-public_date').all()) - f = lambda p: p.tags + f = lambda p: p.tags t = map(f, posts) - + return render(request, 'posts/index.html', {"posts": posts, "tags_list": t}) + def tags(request,tag=""): posts = deque(Post.objects.filter(tags__name=tag)) return render(request, 'posts/index.html', {"posts": posts, "tags_list": None}) - + + def show(request,id=None): if id.isdigit() or id is int: p = Post.objects.get(id=int(id)) @@ -51,9 +44,15 @@ def show(request,id=None): } return render(request, 'posts/show.html', context) -# Ajax function that is called to calculate a slug from the title -def slug_calc(request): +########### +# HELPERS # +########### + +def slug_calc(request): + """ + Ajax function that is called to calculate a slug from the title + """ if request.method == "GET": get = request.GET.copy() title = get['title'] @@ -67,9 +66,11 @@ def slug_calc(request): return HttpResponseServerError("Requires a title field.") -# Ajax function that returns autocomplete suggestions for a given tag input -def tag_complete(request): +def tag_complete(request): + """ + Ajax function that returns autocomplete suggestions for a given tag input + """ if request.method == "GET": get = request.GET.copy() term = get['term'] @@ -81,9 +82,24 @@ def tag_complete(request): tag_array.append(elem.name) return JsonResponse(tag_array, safe=False) - + return HttpResponseServerError("Requires a term field.") + +def get_next_dict(): + #TODO: Docstring + posts = Post.article_objects.order_by('-public_date').values('slug') + print(posts) + d = {} + print(d) + for k,v in enumerate(posts): + if k == len(posts) - 1: + break + d[v['slug']] = posts[k+1]['slug'] + print(d) + return d + + class PostViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. @@ -94,6 +110,6 @@ class PostViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend] filterset_fields = ['legacy_id', 'slug','legacy_rubrik_id'] lookup_field = 'slug' - + def pre_save(self, obj): obj.image = self.request.FILES.get('image') diff --git a/fet2020/templates/members/index.html b/fet2020/templates/members/index.html new file mode 100644 index 00000000..0f334e5b --- /dev/null +++ b/fet2020/templates/members/index.html @@ -0,0 +1,79 @@ +{% extends 'layout.html' %} + +{% block content %} +