Skip to content

Commit

Permalink
Add Tenant
Browse files Browse the repository at this point in the history
- Bind Categories, PDFRequests and Analytics to specific Tenant
- Ton of small fixes on every level
  • Loading branch information
pehala committed Sep 20, 2023
1 parent f8a3b66 commit e2784f5
Show file tree
Hide file tree
Showing 61 changed files with 1,166 additions and 366 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ MANAGE = $(RUN) python manage.py
SETTINGS ?= chords.settings.production

pylint:
$(RUN) pylint backend/ chords/ pdf/ frontend/ category/ analytics/ --django-settings-module=$(SETTINGS)
$(RUN) pylint backend/ chords/ pdf/ frontend/ category/ analytics/ tenants/ --django-settings-module=$(SETTINGS)

black:
$(RUN) black --check . --diff
Expand Down
6 changes: 3 additions & 3 deletions analytics/locale/cs/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-09 19:21+0000\n"
"POT-Creation-Date: 2023-09-10 13:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand All @@ -19,11 +19,11 @@ msgstr ""
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"

#: analytics/models.py:9
#: analytics/models.py:12
msgid "Key"
msgstr "Klíč"

#: analytics/models.py:10
#: analytics/models.py:13
msgid "Hits"
msgstr "Kliknutí"

Expand Down
43 changes: 43 additions & 0 deletions analytics/migrations/0003_daystatistic_tenant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 4.2.5 on 2023-09-10 06:47

from django.db import migrations, models
import django.db.models.deletion


def attach_to_tenant(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
Tenant = apps.get_model("tenants", "Tenant")
DayStatistic = apps.get_model("analytics", "DayStatistic")
db_alias = schema_editor.connection.alias

default_tenant = Tenant.objects.using(db_alias).first()
statistics = DayStatistic.objects.using(db_alias).all()
for day in statistics:
day.tenant = default_tenant

DayStatistic.objects.using(db_alias).bulk_update(statistics, ["tenant"])


class Migration(migrations.Migration):
dependencies = [
("tenants", "0001_initial"),
("analytics", "0002_auto_20211212_0917"),
]

operations = [
migrations.AddField(
model_name="DayStatistic",
name="tenant",
field=models.ForeignKey(
default=None, on_delete=django.db.models.deletion.CASCADE, to="tenants.tenant", null=True
),
preserve_default=False,
),
migrations.RunPython(attach_to_tenant),
migrations.AlterField(
model_name="DayStatistic",
name="tenant",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tenants.tenant"),
),
]
5 changes: 4 additions & 1 deletion analytics/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""Analytics models"""
from django.db.models import Model, CharField, IntegerField, DateField
from django.db.models import Model, CharField, IntegerField, DateField, ForeignKey, CASCADE
from django.utils.translation import gettext_lazy as _

from tenants.models import Tenant


class DayStatistic(Model):
"""Statistic data for given day/key combination"""

tenant = ForeignKey(Tenant, on_delete=CASCADE)
key = CharField(verbose_name=_("Key"), max_length=25)
hits = IntegerField(verbose_name=_("Hits"), default=0)
date = DateField(auto_now_add=True, editable=False)
12 changes: 7 additions & 5 deletions analytics/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from analytics.models import DayStatistic


def analytics(key):
def analytics(request, key):
"""Adds hit"""
with transaction.atomic():
statistic, _ = DayStatistic.objects.get_or_create(key=key, date=now())
statistic, _ = DayStatistic.objects.get_or_create(key=key, date=now(), tenant=request.tenant)
statistic.hits += 1
statistic.save()

Expand All @@ -35,7 +35,7 @@ def get_key(self):

def dispatch(self, request, *args, **kwargs):
result = super().dispatch(request, *args, **kwargs)
analytics(self.get_key())
analytics(request, self.get_key())
return result


Expand Down Expand Up @@ -71,8 +71,10 @@ def get(self, request: HttpRequest, *args, **kwargs):
end_date = datetime.now().date()
key = request.GET["key"]
if len(key) > 0:
days = DayStatistic.objects.filter(date__gte=start_date, date__lte=end_date, key=key)
days = DayStatistic.objects.filter(
date__gte=start_date, date__lte=end_date, key=key, tenant=self.request.tenant
)
else:
days = DayStatistic.objects.filter(date__gte=start_date, date__lte=end_date)
days = DayStatistic.objects.filter(date__gte=start_date, date__lte=end_date, tenant=self.request.tenant)
days = days.values("date").annotate(total=Sum("hits")).values("date", "total").order_by("date")
return JsonResponse({entry["date"].isoformat(): entry["total"] for entry in days})
22 changes: 22 additions & 0 deletions backend/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Helper methods for AuthZ"""


def is_superadmin(request):
"""Returns True, if the current user is super admin"""
return request.user.is_authenticated and request.user.is_superuser


def is_localadmin(request):
"""Returns True, if the current user is Tenant-level administrator"""
user = request.user
return user.is_authenticated and request.tenant.admins.filter(id=user.id).exists() or is_superadmin(request)


def is_authenticated(request):
"""Returns True, if the current user is logged in"""
return request.user.is_authenticated


def is_not_authenticated(request):
"""Returns True, if the current user is NOT logged in"""
return not request.user.is_authenticated
47 changes: 47 additions & 0 deletions backend/auth/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Mixins for AuthZ"""
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin

from backend.auth import is_localadmin, is_superadmin

# pylint: disable=no-member


class LocalAdminRequired(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin for checking if the user can administer current Tenant"""

def test_func(self):
return is_localadmin(self.request)


class SuperAdminRequired(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin for checking if the user can administer the entire site, including Django admin"""

def test_func(self):
return is_superadmin(self.request)


class RegenerateViewMixin:
"""Mixin which tell you if the object changed or not"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.regenerate = None

def form_valid(self, form):
"""Check if there were any changes"""
if len(form.changed_data) > 0:
self.regenerate = True
else:
self.regenerate = False

return super().form_valid(form)


class PassRequestToFormMixin:
"""Passes request to a Form"""

def get_form_kwargs(self):
"""Add request to Form kwargs"""
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
11 changes: 10 additions & 1 deletion backend/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from category.models import Category


class SongForm(ModelForm):
class AdminSongForm(ModelForm):
"""Song form"""

categories = ModelMultipleChoiceField(widget=CheckboxSelectMultiple, queryset=Category.objects.all())
Expand All @@ -14,3 +14,12 @@ class Meta:
model = Song
# pylint: disable=modelform-uses-exclude
exclude = ["prerendered_web", "prerendered_pdf"]


class SongForm(AdminSongForm):
"""Song Form that has only categories from current tenant"""

def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super().__init__(*args, **kwargs)
self.fields["categories"].queryset = Category.objects.filter(tenant=self.request.tenant)
61 changes: 31 additions & 30 deletions backend/locale/cs/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-16 07:45+0000\n"
"POT-Creation-Date: 2023-09-20 18:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand All @@ -19,39 +19,48 @@ msgstr ""
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"

#: backend/menus.py:8
msgid "Songbook List"
msgstr "Seznam zpěvníků"

#: backend/menus.py:9
msgid "Add a song"
msgstr "Přidat písničku"
msgid "Analytics"
msgstr "Statistiky"

#: backend/menus.py:10
msgid "Add Songbook"
msgstr "Přidat zpěvník"
#| msgid "Add Songbook"
msgid "Edit Songbook"
msgstr "Upravit zpěvník"

#: backend/menus.py:11
msgid "Analytics"
msgstr "Statistiky"
msgid "Django Admin"
msgstr ""

#: backend/menus.py:17
msgid "Admin"
msgstr "Administrace"

#: backend/menus.py:25
msgid "Category list"
msgstr "Seznam Kategorii"

#: backend/menus.py:26
msgid "Log in"
msgstr "Přihlásit se"
msgid "Add Category"
msgstr "Přidat Kategorii"

#: backend/menus.py:27
msgid "Add Song"
msgstr "Přidat písničku"

#: backend/menus.py:31
#: backend/menus.py:33 backend/models.py:95
msgid "Songs"
msgstr "Písničky"

#: backend/menus.py:42
msgid "Logout"
msgstr "Odhlásit se"

#: backend/menus.py:36
#: backend/menus.py:46
msgid "Change password"
msgstr "Změna hesla"

#: backend/menus.py:42
#: backend/menus.py:53
msgid "Account"
msgstr "Účet"

Expand All @@ -72,8 +81,8 @@ msgid "Youtube Link"
msgstr "Odkaz na Youtube"

#: backend/models.py:37
msgid "Songbooks"
msgstr "Seznam zpěvníků"
msgid "Categories"
msgstr "Kategorie"

#: backend/models.py:38
msgid "Archived"
Expand All @@ -83,14 +92,10 @@ msgstr "Archivovat"
msgid "Lyrics"
msgstr "Text"

#: backend/models.py:91
#: backend/models.py:94
msgid "Song"
msgstr "Písnička"

#: backend/models.py:92
msgid "Songs"
msgstr "Písničky"

#: backend/templates/songs/add.html:4 backend/templates/songs/add.html:5
msgid "Song Editor"
msgstr "Editor písničky"
Expand Down Expand Up @@ -139,21 +144,17 @@ msgstr "Hledat"
msgid "Number, text or author"
msgstr "Číslo, text nebo autor"

#: backend/views.py:93
msgid "Index Page"
msgstr "Úvodní stránka"

#: backend/views.py:113
#: backend/views.py:88
#, python-format
msgid "Song %(name)s was successfully created"
msgstr "Písnička %(name)s byla úspěšně přidána"

#: backend/views.py:130
#: backend/views.py:104
#, python-format
msgid "Song %(name)s was successfully updated"
msgstr "Písnička %(name)s byla úspěšně upravena"

#: backend/views.py:147
#: backend/views.py:120
#, python-format
msgid "Song %s was successfully deleted"
msgstr "Písnička %s byla úspěšně odstraněna"
30 changes: 22 additions & 8 deletions backend/menus.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Menus for backend app"""
from django.urls import reverse
from django.utils.translation import gettext_noop as _

from menu import MenuItem, Menu

from backend.auth import is_authenticated, is_localadmin, is_superadmin

admin_children = (
MenuItem(_("Songbook List"), reverse("category:list")),
MenuItem(_("Add a song"), reverse("backend:add")),
MenuItem(_("Add Songbook"), reverse("category:add")),
MenuItem(_("Analytics"), reverse("analytics:index")),
MenuItem(_("Edit Songbook"), reverse("tenants:edit"), check=is_localadmin),
MenuItem(_("Django Admin"), reverse("admin:index"), check=is_superadmin),
)

Menu.add_item(
Expand All @@ -17,7 +17,23 @@
_("Admin"),
reverse("backend:index"),
children=admin_children,
check=lambda request: request.user.is_authenticated,
check=is_localadmin,
),
)

songbook_children = (
MenuItem(_("Category list"), reverse("category:list")),
MenuItem(_("Add Category"), reverse("category:add")),
MenuItem(_("Add Song"), reverse("backend:add")),
)

Menu.add_item(
"songbook-admin",
MenuItem(
_("Songs"),
reverse("backend:index"),
children=songbook_children,
check=is_localadmin,
),
)

Expand All @@ -34,7 +50,5 @@

Menu.add_item(
"account",
MenuItem(
_("Account"), reverse("login"), children=account_children, check=lambda request: request.user.is_authenticated
),
MenuItem(_("Account"), reverse("login"), children=account_children, check=is_authenticated),
)
Loading

0 comments on commit e2784f5

Please sign in to comment.