Skip to content

Commit

Permalink
Add user management
Browse files Browse the repository at this point in the history
- Add user model, views and templates
- Add organization model, views and templates
- Create, edit and delete superusers and staff (fixes #44)
- Create, edit and delete region users (fixes #59)
- Show region switcher based on user's permissions (fixes #56)
- Show link to network management if user is superuser or staff (fixes #66)
  • Loading branch information
timobrembeck committed Jun 27, 2019
1 parent 17380d7 commit 98a28e7
Show file tree
Hide file tree
Showing 37 changed files with 1,356 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ docs/_build/
.idea
.directory
.directory
cms-django.iml

# Media Library
backend/media/*

# XLIFF files folder
**/xliffs/

4 changes: 2 additions & 2 deletions backend/cms/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@


from django.contrib import admin

from .models import Site, Language, LanguageTreeNode, Page, PageTranslation
from .models import Site, Language, LanguageTreeNode, Page, PageTranslation, Organization

admin.site.register(Site)
admin.site.register(Language)
admin.site.register(LanguageTreeNode)
admin.site.register(Page)
admin.site.register(PageTranslation)
admin.site.register(Organization)
5 changes: 5 additions & 0 deletions backend/cms/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@

from .media_models import Document
from .media_models import DocumentForm

from .configuration import Configuration

from .organization import Organization

from .user_profile import UserProfile
6 changes: 3 additions & 3 deletions backend/cms/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from datetime import datetime, time, date

from dateutil.rrule import weekday, rrule
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
Expand Down Expand Up @@ -113,7 +113,7 @@ def get_list_view(cls):

event_translations = EventTranslation.objects.filter(
language='de'
).select_related('user')
).select_related('creator')
events = cls.objects.all().prefetch_related(
models.Prefetch('event_translations', queryset=event_translations)
).filter(event_translations__language='de')
Expand Down Expand Up @@ -178,4 +178,4 @@ class EventTranslation(models.Model):
event = models.ForeignKey(Event, related_name='event_translations', on_delete=models.CASCADE)
created_date = models.DateTimeField(default=timezone.now)
last_updated = models.DateTimeField(auto_now=True)
creator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
15 changes: 15 additions & 0 deletions backend/cms/models/organization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.db import models
from django.utils import timezone


class Organization(models.Model):

name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
thumbnail = models.CharField(max_length=250, blank=True)

created_date = models.DateTimeField(default=timezone.now)
last_updated = models.DateTimeField(auto_now=True)

def __str__(self):
return self.name
5 changes: 2 additions & 3 deletions backend/cms/models/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
Expand Down Expand Up @@ -161,7 +160,7 @@ class PageTranslation(models.Model):
version = models.PositiveIntegerField(default=0)
public = models.BooleanField(default=False)
minor_edit = models.BooleanField(default=False)
creator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
created_date = models.DateTimeField(default=timezone.now)
last_updated = models.DateTimeField(auto_now=True)

Expand Down
6 changes: 3 additions & 3 deletions backend/cms/models/poi.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Model for Point of Interests
"""
from django.contrib.auth.models import User
from django.db import models
from django.conf import settings
from django.utils import timezone

from .site import Site
Expand Down Expand Up @@ -35,7 +35,7 @@ def get_list_view(cls):

poi_translations = POITranslation.objects.filter(
language='de'
).select_related('user')
).select_related('creator')
pois = cls.objects.all().prefetch_related(
models.Prefetch('poi_translations', queryset=poi_translations)
).filter(poi_translations__language='de')
Expand Down Expand Up @@ -66,4 +66,4 @@ class POITranslation(models.Model):
public = models.BooleanField(default=False)
created_date = models.DateTimeField(default=timezone.now)
last_updated = models.DateTimeField(auto_now=True)
creator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
22 changes: 22 additions & 0 deletions backend/cms/models/user_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db import models
from django.conf import settings

from .site import Site
from .organization import Organization


class UserProfile(models.Model):

user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
regions = models.ManyToManyField(Site, blank=True)
organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.SET_NULL)

@property
def roles(self):
return self.user.groups.all()

def __str__(self):
return self.user.username

class Meta():
default_permissions = ()
56 changes: 34 additions & 22 deletions backend/cms/templates/_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@
{% endif %}
</div>
<div id="instance-selector-list" class="absolute shadow rounded-b">
{% for other_region in sites %}
<a href="{% url 'dashboard' site_slug=other_region.slug %}" class="block px-4 py-3 text-grey-darkest hover:bg-grey">
{{ other_region.name }}
</a>
{% for other_region in user.profile.regions.all %}
{% if not other_region == site %}
<a href="{% url 'dashboard' site_slug=other_region.slug %}" class="block px-4 py-3 text-grey-darkest hover:bg-grey">
{{ other_region.name }}
</a>
{% endif %}
{% endfor %}
{% if site %}
<a href="{% url 'admin_dashboard' %}" class="block px-4 py-3 text-grey-darkest hover:bg-grey rounded-b">
{% trans 'Network Management' %}
</a>
{% if user.is_superuser or user.is_staff %}
{% if site %}
<a href="{% url 'admin_dashboard' %}" class="block px-4 py-3 text-grey-darkest hover:bg-grey rounded-b">
{% trans 'Network Management' %}
</a>
{% endif %}
{% endif %}
</div>
</div>
Expand Down Expand Up @@ -102,7 +106,7 @@
</div>
<div id="menu" class="pb-2">
{% if site %}
<a href="{% url 'dashboard' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'dashboard' %}active{% endif %}">
<a href="{% url 'dashboard' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'region_dashboard' %}active{% endif %}">
<i data-feather="grid" class="absolute"></i>
{% trans 'My Dashboard' %}
</a>
Expand All @@ -122,35 +126,35 @@
<i data-feather="layout" class="absolute"></i>
{% trans 'Pages' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'events' %}active{% endif %}">
<i data-feather="calendar" class="absolute"></i>
{% trans 'Events' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="{% url 'region_users' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'region_users' %}active{% endif %}">
<i data-feather="users" class="absolute"></i>
{% trans 'User' %}
{% trans 'Users' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'region_feedback' %}active{% endif %}">
<i data-feather="thumbs-up" class="absolute"></i>
{% trans 'Feedback' %}
</a>
<a href="{% url 'push_notifications' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'push_notifications' %}active{% endif %}">
<i data-feather="send" class="absolute"></i>
{% trans 'Push Notifications' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'pdf_export' %}active{% endif %}">
<i data-feather="layers" class="absolute"></i>
{% trans 'PDF Export' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'imprint' %}active{% endif %}">
<i data-feather="file-text" class="absolute"></i>
{% trans 'Imprint' %}
</a>
<a href="{% url 'language_tree' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'language_tree' %}active{% endif %}">
<i data-feather="flag" class="absolute"></i>
{% trans 'Language tree' %}
</a>
<a href="{% url 'settings' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="{% url 'settings' site_slug=site.slug %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'region_settings' %}active{% endif %}">
<i data-feather="sliders" class="absolute"></i>
{% trans 'Settings' %}
</a>
Expand All @@ -167,15 +171,23 @@
<i data-feather="flag" class="absolute"></i>
{% trans 'Languages' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<i data-feather="users" class="absolute"></i>
{% trans 'User' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat">
<a href="{% url 'users' %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'users' %}active{% endif %}">
<i data-feather="users" class="absolute"></i>
{% trans 'Users' %}
</a>
<a href="{% url 'roles' %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'roles' %}active{% endif %}">
<i data-feather="key" class="absolute"></i>
{% trans 'Roles' %}
</a>
<a href="{% url 'organizations' %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'organizations' %}active{% endif %}">
<i data-feather="umbrella" class="absolute"></i>
{% trans 'Organizations' %}
</a>
<a href="" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'admin_feedback' %}active{% endif %}">
<i data-feather="thumbs-up" class="absolute"></i>
{% trans 'Feedback' %}
</a>
<a href="{% url 'admin_settings' %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat{% if current_menu_item == 'settings' %}active{% endif %}">
<a href="{% url 'admin_settings' %}" class="relative block text-grey-light hover:text-grey-darker hover:bg-integreat {% if current_menu_item == 'admin_settings' %}active{% endif %}">
<i data-feather="sliders" class="absolute"></i>
{% trans 'Settings' %}
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% load i18n %}
<div id="confirm_delete_user" class="confirmation-popup flex-col justify-center max-w-sm fixed pin-t pin-b pin-r pin-l hidden">
<div class="content bg-grey-light w-full p-4 shadow-md rounded">
<h2>{% trans 'Please confirm that you really want to delete this user' %}</h2>
<p class="mt-4 mb-6">
{% trans 'This cannot be reversed.' %}
</p>
<a href="{% url 'delete_region_user' site_slug=site.slug user_id=user_form.instance.id %}" class="bg-red hover:bg-red-dark text-white font-bold py-3 px-4 mr-2 rounded focus:outline-none focus:shadow-outline">
{% trans 'Yes, delete this user now' %}
</a>
<button class="cursor-pointer bg-blue hover:bg-blue-dark text-white font-bold py-3 px-4 rounded" onclick="close_confirmation_popup(event)">
{% trans 'Cancel' %}
</button>
</div>
</div>
15 changes: 15 additions & 0 deletions backend/cms/templates/general/confirmation_popups/delete_user.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% load i18n %}
<div id="confirm_delete_user" class="confirmation-popup flex-col justify-center max-w-sm fixed pin-t pin-b pin-r pin-l hidden">
<div class="content bg-grey-light w-full p-4 shadow-md rounded">
<h2>{% trans 'Please confirm that you really want to delete this user' %}</h2>
<p class="mt-4 mb-6">
{% trans 'This cannot be reversed.' %}
</p>
<a href="{% url 'delete_user' user_id=user_form.instance.id %}" class="bg-red hover:bg-red-dark text-white font-bold py-3 px-4 mr-2 rounded focus:outline-none focus:shadow-outline">
{% trans 'Yes, delete this user now' %}
</a>
<button class="cursor-pointer bg-blue hover:bg-blue-dark text-white font-bold py-3 px-4 rounded" onclick="close_confirmation_popup(event)">
{% trans 'Cancel' %}
</button>
</div>
</div>
41 changes: 41 additions & 0 deletions backend/cms/templates/organizations/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{% extends "_base.html" %}
{% load i18n %}
{% block content %}
{% load static %}
<div class="table-header">
<div class="flex flex-wrap">
<form class="table-search relative w-1/2">
<i data-feather="search" class="absolute"></i>
<input type="search" placeholder="{% trans 'Search' %}" class="h-full pl-10 pr-4 rounded shadow">
</form>
<div class="w-1/2 flex flex-wrap justify-end">
<a href="{% url 'new_organization' %}" class="bg-grey-dark hover:bg-integreat hover:text-grey-darkest text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
{% trans 'Create organization' %}
</a>
</div>
</div>
</div>

<div class="table-listing">
<table class="w-full mt-4 rounded border border-solid border-grey-light shadow bg-white">
<thead>
<tr class="border-b border-solid border-grey-light">
<th class="text-sm text-left uppercase py-3 pl-4 pr-2">{% trans 'Name' %}</th>
<th class="text-sm text-left uppercase py-3 px-2">{% trans 'Slug' %}</th>
<th class="text-sm text-left uppercase py-3 px-2">{% trans 'Thumbnail' %}</th>
</tr>
</thead>
<tbody>
{% for organization in organizations %}
{% include "organizations/list_row.html" %}
{% empty %}
<tr>
<td colspan="6" class="px-2 py-3">
{% trans 'No organizations available yet.' %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
13 changes: 13 additions & 0 deletions backend/cms/templates/organizations/list_row.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<tr class="border-t border-solid border-grey-lighter hover:bg-grey-lightest">
<td>
<a href="{% url 'edit_organization' organization_id=organization.id %}" class="block py-3 px-2 text-grey-darkest">
{{ organization.name }}
</a>
</td>
<td>
{{ organization.slug }}
</td>
<td>
{{ organization.thumbnail }}
</td>
</tr>

0 comments on commit 98a28e7

Please sign in to comment.