From dbf5f721ce08e5bb731bb7b1a9aa5ac4cd769055 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Sun, 29 Aug 2021 00:20:02 +0200 Subject: [PATCH] multi-tenant-system --- campaigns/__init__.py | 0 campaigns/admin.py | 9 +++ campaigns/apps.py | 6 ++ campaigns/migrations/0001_initial.py | 42 ++++++++++ campaigns/migrations/0002_campaign_owner.py | 23 ++++++ campaigns/migrations/__init__.py | 0 campaigns/models.py | 24 ++++++ campaigns/tests.py | 3 + campaigns/views.py | 3 + notes/admin.py | 3 +- notes/forms.py | 7 +- notes/migrations/0014_auto_20210828_2311.py | 87 +++++++++++++++++++++ notes/migrations/0015_auto_20210828_2323.py | 28 +++++++ notes/models/__init__.py | 1 - notes/models/basemodel.py | 6 +- notes/models/campaign.py | 21 ----- notes/models/character.py | 2 +- notes/models/ingameday.py | 7 +- notes/models/loot.py | 4 +- notes/models/session.py | 7 +- notes/templates/navbarbase.html | 12 +-- notes/templates/notes/character_detail.html | 4 +- notes/templates/notes/loot_edit.html | 2 +- notes/templates/notes/loot_overview.html | 4 +- notes/urls.py | 38 ++++----- notes/utils/money.py | 5 +- notes/views/__init__.py | 2 +- notes/views/character_views.py | 26 +++--- notes/views/day_views.py | 17 ++-- notes/views/loot_views.py | 19 ++--- poetry.lock | 38 ++++++++- pyproject.toml | 2 + rpg_notes/settings.py | 48 ++++++++++-- templates/registration/login.html | 28 +++++++ users/__init__.py | 0 users/admin.py | 8 ++ users/apps.py | 6 ++ users/migrations/0001_initial.py | 33 ++++++++ users/migrations/__init__.py | 0 users/models.py | 11 +++ users/tests.py | 3 + users/views.py | 3 + 42 files changed, 464 insertions(+), 128 deletions(-) create mode 100644 campaigns/__init__.py create mode 100644 campaigns/admin.py create mode 100644 campaigns/apps.py create mode 100644 campaigns/migrations/0001_initial.py create mode 100644 campaigns/migrations/0002_campaign_owner.py create mode 100644 campaigns/migrations/__init__.py create mode 100644 campaigns/models.py create mode 100644 campaigns/tests.py create mode 100644 campaigns/views.py create mode 100644 notes/migrations/0014_auto_20210828_2311.py create mode 100644 notes/migrations/0015_auto_20210828_2323.py delete mode 100644 notes/models/campaign.py create mode 100644 templates/registration/login.html create mode 100644 users/__init__.py create mode 100644 users/admin.py create mode 100644 users/apps.py create mode 100644 users/migrations/0001_initial.py create mode 100644 users/migrations/__init__.py create mode 100644 users/models.py create mode 100644 users/tests.py create mode 100644 users/views.py diff --git a/campaigns/__init__.py b/campaigns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/admin.py b/campaigns/admin.py new file mode 100644 index 0000000..b721226 --- /dev/null +++ b/campaigns/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from django_tenants.admin import TenantAdminMixin + +from campaigns.models import Campaign + + +@admin.register(Campaign) +class ClientAdmin(TenantAdminMixin, admin.ModelAdmin): + list_display = ('name',) diff --git a/campaigns/apps.py b/campaigns/apps.py new file mode 100644 index 0000000..0cab42c --- /dev/null +++ b/campaigns/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CampaignsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'campaigns' diff --git a/campaigns/migrations/0001_initial.py b/campaigns/migrations/0001_initial.py new file mode 100644 index 0000000..33f45cc --- /dev/null +++ b/campaigns/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.6 on 2021-08-28 21:33 + +from django.db import migrations, models +import django.db.models.deletion +import django_tenants.postgresql_backend.base + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Campaign', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('schema_name', models.CharField(db_index=True, max_length=63, unique=True, validators=[django_tenants.postgresql_backend.base._check_schema_name])), + ('slug', models.SlugField(blank=True, verbose_name='Tenant URL Name')), + ('created', models.DateTimeField()), + ('modified', models.DateTimeField(blank=True)), + ('name', models.CharField(max_length=1000)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Domain', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('domain', models.CharField(db_index=True, max_length=253, unique=True)), + ('is_primary', models.BooleanField(db_index=True, default=True)), + ('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domains', to='campaigns.campaign')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/campaigns/migrations/0002_campaign_owner.py b/campaigns/migrations/0002_campaign_owner.py new file mode 100644 index 0000000..123e397 --- /dev/null +++ b/campaigns/migrations/0002_campaign_owner.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.6 on 2021-08-28 21:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('campaigns', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='campaign', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/campaigns/migrations/__init__.py b/campaigns/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/models.py b/campaigns/models.py new file mode 100644 index 0000000..38b7183 --- /dev/null +++ b/campaigns/models.py @@ -0,0 +1,24 @@ +from django.db import models +# Create your models here. +from django_tenants.models import DomainMixin +from tenant_users.tenants.models import TenantBase + +from rpg_notes.secrets import DEBUG + + +class Campaign(TenantBase): + name = models.CharField(max_length=1000) + + auto_create_schema = True + + def __str__(self): + return self.name + + def get_absolute_url(self): + print(self.get_primary_domain().domain) + protocol = "http://" if DEBUG else "https://" + return protocol + self.get_primary_domain().domain + (":8000" if DEBUG else "") + + +class Domain(DomainMixin): + pass diff --git a/campaigns/tests.py b/campaigns/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/campaigns/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/campaigns/views.py b/campaigns/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/campaigns/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/notes/admin.py b/notes/admin.py index 8fe34d9..e860350 100644 --- a/notes/admin.py +++ b/notes/admin.py @@ -2,9 +2,8 @@ from django.contrib import admin # Register your models here. from simple_history.admin import SimpleHistoryAdmin -from notes.models import Character, Campaign, Faction, Location, Loot, IngameDay, Session +from notes.models import Character, Faction, Location, Loot, IngameDay, Session -admin.site.register(Campaign, SimpleHistoryAdmin) admin.site.register(Character, SimpleHistoryAdmin) admin.site.register(Faction, SimpleHistoryAdmin) admin.site.register(Location, SimpleHistoryAdmin) diff --git a/notes/forms.py b/notes/forms.py index 95bd51d..8ced11f 100644 --- a/notes/forms.py +++ b/notes/forms.py @@ -1,13 +1,8 @@ from django.forms import ModelForm, ModelMultipleChoiceField, CheckboxSelectMultiple -from notes.models import Campaign, Loot, Character, IngameDay, Session +from notes.models import Loot, Character, IngameDay, Session -class CampaignForm(ModelForm): - class Meta: - model = Campaign - fields = "__all__" - class LootForm(ModelForm): class Meta: diff --git a/notes/migrations/0014_auto_20210828_2311.py b/notes/migrations/0014_auto_20210828_2311.py new file mode 100644 index 0000000..db3dbd6 --- /dev/null +++ b/notes/migrations/0014_auto_20210828_2311.py @@ -0,0 +1,87 @@ +# Generated by Django 3.2.6 on 2021-08-28 21:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0013_auto_20210823_1405'), + ] + + operations = [ + migrations.RemoveField( + model_name='campaign', + name='dm', + ), + migrations.RemoveField( + model_name='character', + name='campaign', + ), + migrations.RemoveField( + model_name='historicalcharacter', + name='campaign', + ), + migrations.RemoveField( + model_name='historicalfaction', + name='campaign', + ), + migrations.RemoveField( + model_name='historicalingameday', + name='campaign', + ), + migrations.RemoveField( + model_name='historicallocation', + name='campaign', + ), + migrations.RemoveField( + model_name='historicalloot', + name='campaign', + ), + migrations.RemoveField( + model_name='historicalsession', + name='campaign', + ), + migrations.RemoveField( + model_name='loot', + name='campaign', + ), + migrations.AlterUniqueTogether( + name='faction', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ingameday', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='location', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='session', + unique_together=set(), + ), + migrations.DeleteModel( + name='HistoricalCampaign', + ), + migrations.RemoveField( + model_name='faction', + name='campaign', + ), + migrations.RemoveField( + model_name='ingameday', + name='campaign', + ), + migrations.RemoveField( + model_name='location', + name='campaign', + ), + migrations.RemoveField( + model_name='session', + name='campaign', + ), + migrations.DeleteModel( + name='Campaign', + ), + ] diff --git a/notes/migrations/0015_auto_20210828_2323.py b/notes/migrations/0015_auto_20210828_2323.py new file mode 100644 index 0000000..46faedb --- /dev/null +++ b/notes/migrations/0015_auto_20210828_2323.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.6 on 2021-08-28 21:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0014_auto_20210828_2311'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + migrations.AlterField( + model_name='faction', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + migrations.AlterField( + model_name='location', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + ] diff --git a/notes/models/__init__.py b/notes/models/__init__.py index def5a8c..8176861 100644 --- a/notes/models/__init__.py +++ b/notes/models/__init__.py @@ -1,5 +1,4 @@ from .descriptionmodel import DescriptionModel -from .campaign import Campaign from .basemodel import BaseModel from .faction import Faction from .location import Location diff --git a/notes/models/basemodel.py b/notes/models/basemodel.py index b872f6a..bee8461 100644 --- a/notes/models/basemodel.py +++ b/notes/models/basemodel.py @@ -1,17 +1,13 @@ from django.db import models from django.utils.text import slugify -from notes.models import Campaign - class BaseModel(models.Model): - campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) name = models.CharField(max_length=1000) - slug = models.SlugField(editable=False) + slug = models.SlugField(editable=False, unique=True) class Meta: abstract = True - unique_together = ["campaign", "slug"] def save(self, *args, **kwargs): if not self.id: diff --git a/notes/models/campaign.py b/notes/models/campaign.py deleted file mode 100644 index 6f7b649..0000000 --- a/notes/models/campaign.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.conf import settings -from django.db import models -from django.urls import reverse -from simple_history.models import HistoricalRecords - -from notes.models import DescriptionModel - - -class Campaign(DescriptionModel): - name = models.CharField(max_length=1000) - slug = models.SlugField(unique=True, editable=False) - dm = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) - created = models.DateTimeField(auto_now_add=True) - last_modified = models.DateTimeField(auto_now=True) - history = HistoricalRecords() - - def get_absolute_url(self): - return reverse('campaigndetail', args=[str(self.slug)]) - - def __str__(self): - return self.name diff --git a/notes/models/character.py b/notes/models/character.py index 9a3b89f..0db5837 100644 --- a/notes/models/character.py +++ b/notes/models/character.py @@ -24,7 +24,7 @@ class Character(BaseModel, DescriptionModel): ordering = ["name"] def get_absolute_url(self): - return reverse('characterdetail', args=[self.campaign.slug, self.slug]) + return reverse('characterdetail', args=[self.slug]) def initials(self): return "".join([word[0] for word in self.name.split()][:2]).upper() diff --git a/notes/models/ingameday.py b/notes/models/ingameday.py index c88b03b..ac6196b 100644 --- a/notes/models/ingameday.py +++ b/notes/models/ingameday.py @@ -3,12 +3,10 @@ from django.db import models from django.urls import reverse from simple_history.models import HistoricalRecords -from notes.models import Campaign, Session, DescriptionModel +from notes.models import Session, DescriptionModel class IngameDay(DescriptionModel): - campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) - day = models.PositiveIntegerField() sessions = models.ManyToManyField(Session, related_name="ingame_days") @@ -18,10 +16,9 @@ class IngameDay(DescriptionModel): class Meta: ordering = ["-day"] - unique_together = ["campaign", "day"] def get_absolute_url(self): - return reverse('daydetail', args=[self.campaign.slug, self.day]) + return reverse('daydetail', args=[self.day]) @property def prettyname(self): diff --git a/notes/models/loot.py b/notes/models/loot.py index ca1282f..dd7768f 100644 --- a/notes/models/loot.py +++ b/notes/models/loot.py @@ -2,12 +2,10 @@ from django.conf import settings from django.db import models from simple_history.models import HistoricalRecords -from notes.models import Campaign, DescriptionModel +from notes.models import DescriptionModel class Loot(DescriptionModel): - campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) - name = models.CharField(max_length=1000) quantity = models.PositiveSmallIntegerField() value_gold = models.DecimalField("Value (Gold)", max_digits=7, decimal_places=2) diff --git a/notes/models/session.py b/notes/models/session.py index 8257f0b..62de6fe 100644 --- a/notes/models/session.py +++ b/notes/models/session.py @@ -4,12 +4,8 @@ from django.db import models from django.urls import reverse from simple_history.models import HistoricalRecords -from notes.models import Campaign - class Session(models.Model): - campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) - day = models.DateField(default=date.today) created = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now=True) @@ -17,10 +13,9 @@ class Session(models.Model): class Meta: ordering = ["-day"] - unique_together = ["campaign", "day"] def get_absolute_url(self): - return reverse('sessiondetail', args=[self.campaign.slug, self.id]) + return reverse('sessiondetail', args=[self.id]) def __str__(self): return str(self.day) diff --git a/notes/templates/navbarbase.html b/notes/templates/navbarbase.html index 79d9dd5..98a912c 100644 --- a/notes/templates/navbarbase.html +++ b/notes/templates/navbarbase.html @@ -9,17 +9,17 @@ diff --git a/notes/templates/notes/character_detail.html b/notes/templates/notes/character_detail.html index 901da47..be1ecb1 100644 --- a/notes/templates/notes/character_detail.html +++ b/notes/templates/notes/character_detail.html @@ -16,7 +16,7 @@ {% include "notes/macros/character-pillar.html" with character=c %} {% endfor %} - Add Character + Add Character
@@ -35,7 +35,7 @@

{{ character.name }} - + edit

diff --git a/notes/templates/notes/loot_edit.html b/notes/templates/notes/loot_edit.html index c427b34..8cb8c94 100644 --- a/notes/templates/notes/loot_edit.html +++ b/notes/templates/notes/loot_edit.html @@ -6,7 +6,7 @@ {% if edit %}

Edit "{{ object.name }}"

{% if request.resolver_match.view_name %} - Delete + Delete {% endif %} {% else %}

Add new

diff --git a/notes/templates/notes/loot_overview.html b/notes/templates/notes/loot_overview.html index 3832335..9e4ff92 100644 --- a/notes/templates/notes/loot_overview.html +++ b/notes/templates/notes/loot_overview.html @@ -34,7 +34,7 @@
-

{{ l.name }} edit

+

{{ l.name }} edit

{{ l.description_html|safe }}

Value each: {{ l.value_per_unit|format_money_html }}

@@ -47,7 +47,7 @@
Total value:
{{ total_value|format_money_html }}
- Add Loot + Add Loot {% endblock %} diff --git a/notes/urls.py b/notes/urls.py index 0964906..14051f9 100644 --- a/notes/urls.py +++ b/notes/urls.py @@ -3,23 +3,23 @@ from django.urls import path from notes import views urlpatterns = [ - path("", views.CampaignListView.as_view(), name="campaignlist"), - path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"), - path("c/", views.CampaignDetailView.as_view(), name="campaigndetail"), - path("c//edit", views.CampaignEditView.as_view(), name="campaignedit"), - path("c//delete", views.CampaignDeleteView.as_view(), name="campaigndelete"), - path("c//loot", views.LootListView.as_view(), name="lootlist"), - path("c//loot//edit", views.LootEditView.as_view(), name="lootedit"), - path("c//loot//delete", views.LootDeleteView.as_view(), name="lootdelete"), - path("c//loot/add", views.LootCreateView.as_view(), name="lootadd"), - path("c//character/", views.list_character_redirect, name="characterlist"), - path("c//character/add", views.CharacterCreateView.as_view(), name="characteradd"), - path("c//character/", views.CharacterDetailView.as_view(), name="characterdetail"), - path("c//character//edit", views.CharacterEditView.as_view(), name="characteredit"), - path("c//character//delete", views.CharacterDeleteView.as_view(), name="characterdelete"), - path("c//day/", views.list_day_redirect, name="daylist"), - path("c//day/add", views.DayCreateView.as_view(), name="dayadd"), - path("c//day/", views.DayDetailView.as_view(), name="daydetail"), - path("c//day//edit", views.DayEditView.as_view(), name="dayedit"), - path("c//day//delete", views.DayDeleteView.as_view(), name="daydelete"), + # path("", views.CampaignListView.as_view(), name="campaignlist"), + # path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"), + # path("c/", views.CampaignDetailView.as_view(), name="campaigndetail"), + # path("c//edit", views.CampaignEditView.as_view(), name="campaignedit"), + # path("c//delete", views.CampaignDeleteView.as_view(), name="campaigndelete"), + path("loot", views.LootListView.as_view(), name="lootlist"), + path("loot//edit", views.LootEditView.as_view(), name="lootedit"), + path("loot//delete", views.LootDeleteView.as_view(), name="lootdelete"), + path("loot/add", views.LootCreateView.as_view(), name="lootadd"), + path("character/", views.list_character_redirect, name="characterlist"), + path("character/add", views.CharacterCreateView.as_view(), name="characteradd"), + path("character/", views.CharacterDetailView.as_view(), name="characterdetail"), + path("character//edit", views.CharacterEditView.as_view(), name="characteredit"), + path("character//delete", views.CharacterDeleteView.as_view(), name="characterdelete"), + path("day/", views.list_day_redirect, name="daylist"), + path("day/add", views.DayCreateView.as_view(), name="dayadd"), + path("day/", views.DayDetailView.as_view(), name="daydetail"), + path("day//edit", views.DayEditView.as_view(), name="dayedit"), + path("day//delete", views.DayDeleteView.as_view(), name="daydelete"), ] diff --git a/notes/utils/money.py b/notes/utils/money.py index b7aa244..cee0256 100644 --- a/notes/utils/money.py +++ b/notes/utils/money.py @@ -13,7 +13,8 @@ currencies_HTML = [ "PP" ] -use_platinum=False +use_platinum = False + def digitize(n): print("n", n) @@ -33,6 +34,8 @@ def digitize(n): def format_money(money: Decimal, html=False) -> str: + if not money: + return "" currencies = currencies_HTML if html else currencies_text output = [] cp = round(money / copper, ndigits=0) diff --git a/notes/views/__init__.py b/notes/views/__init__.py index 82bc6f3..7d3176e 100644 --- a/notes/views/__init__.py +++ b/notes/views/__init__.py @@ -1,4 +1,4 @@ -from .campaign_views import * +# from .campaign_views import * from .loot_views import * from .character_views import * from .day_views import * diff --git a/notes/views/character_views.py b/notes/views/character_views.py index e30721e..5723f36 100644 --- a/notes/views/character_views.py +++ b/notes/views/character_views.py @@ -3,7 +3,7 @@ from django.urls import reverse_lazy from django.views import generic from notes.forms import CharacterForm -from notes.models import Character, Campaign +from notes.models import Character # class CharacterListView(generic.ListView): @@ -15,8 +15,9 @@ from notes.models import Character, Campaign # def list_character_redirect(request, *args, **kwargs): - first_character:Character=Character.objects.first() - + first_character: Character = Character.objects.first() + if not first_character: + return redirect("characteradd") return redirect(first_character) @@ -28,18 +29,17 @@ class CharacterDetailView(generic.DetailView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) data["player_characters"] = Character.objects.filter( - campaign__slug=self.kwargs['campslug'], player__isnull=False ).select_related() data["npcs"] = Character.objects.filter( - campaign__slug=self.kwargs['campslug'], player__isnull=True + player__isnull=True ).select_related() return data - def get_object(self, queryset=None): - return Character.objects.get( - campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug'] - ) + # def get_object(self, queryset=None): + # return Character.objects.get( + # campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug'] + # ) class CharacterCreateView(generic.CreateView): @@ -48,18 +48,14 @@ class CharacterCreateView(generic.CreateView): form_class = CharacterForm context_object_name = "object" - def form_valid(self, form): - form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug']) - return super().form_valid(form) - class CharacterEditView(generic.UpdateView): template_name = "notes/loot_edit.html" model = Character form_class = CharacterForm - def get_object(self, queryset=None): - return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']) + # def get_object(self, queryset=None): + # return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']) def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) diff --git a/notes/views/day_views.py b/notes/views/day_views.py index 790d5f3..bfcd29b 100644 --- a/notes/views/day_views.py +++ b/notes/views/day_views.py @@ -3,11 +3,13 @@ from django.urls import reverse_lazy from django.views import generic from notes.forms import DayForm -from notes.models import Session, Campaign, IngameDay +from notes.models import IngameDay def list_day_redirect(request, *args, **kwargs): latest_day: IngameDay = IngameDay.objects.first() + if not latest_day: + return redirect("dayadd") return redirect(latest_day) @@ -31,10 +33,6 @@ class DayCreateView(generic.CreateView): form_class = DayForm context_object_name = "object" - def form_valid(self, form): - form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug']) - return super().form_valid(form) - class DayEditView(generic.UpdateView): template_name = "notes/loot_edit.html" @@ -46,13 +44,14 @@ class DayEditView(generic.UpdateView): data['edit'] = True return data - def get_object(self, queryset=None): - return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) + # def get_object(self, queryset=None): + # return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) + class DayDeleteView(generic.DeleteView): template_name = "notes/campaign_confirm_delete.html" model = IngameDay success_url = reverse_lazy('daylist') - def get_object(self, queryset=None): - return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) + # def get_object(self, queryset=None): + # return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) diff --git a/notes/views/loot_views.py b/notes/views/loot_views.py index 8d536c0..81d0ee8 100644 --- a/notes/views/loot_views.py +++ b/notes/views/loot_views.py @@ -3,7 +3,7 @@ from django.urls import reverse_lazy, reverse from django.views import generic from notes.forms import LootForm -from notes.models import Loot, Campaign +from notes.models import Loot class LootListView(generic.ListView): @@ -11,8 +11,8 @@ class LootListView(generic.ListView): model = Loot context_object_name = "loot" - def get_queryset(self): - return Loot.objects.filter(campaign__slug=self.kwargs['campslug']) + # def get_queryset(self): + # return Loot.objects.filter(campaign__slug=self.kwargs['campslug']) def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) @@ -31,12 +31,8 @@ class LootCreateView(generic.CreateView): model = Loot form_class = LootForm - def form_valid(self, form): - form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug']) - return super().form_valid(form) + success_url = reverse_lazy("lootlist") - def get_success_url(self): - return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")}) class LootEditView(generic.UpdateView): @@ -45,8 +41,7 @@ class LootEditView(generic.UpdateView): form_class = LootForm context_object_name = "object" - def get_success_url(self): - return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")}) + success_url = reverse_lazy("lootlist") def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) @@ -57,5 +52,5 @@ class LootEditView(generic.UpdateView): class LootDeleteView(generic.DeleteView): template_name = "notes/campaign_confirm_delete.html" model = Loot - def get_success_url(self): - return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")}) + + success_url = reverse_lazy("lootlist") diff --git a/poetry.lock b/poetry.lock index 4d75943..4ed15e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,7 +26,7 @@ lxml = ["lxml"] [[package]] name = "bleach" -version = "4.0.0" +version = "4.1.0" description = "An easy safelist-based HTML-sanitizing tool." category = "main" optional = false @@ -94,6 +94,32 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "django-tenant-users" +version = "0.3.12" +description = "A Django app to extend django-tenants to incorporate global multi-tenant users" +category = "main" +optional = false +python-versions = "^3.6.2" +develop = false + +[package.source] +type = "git" +url = "https://github.com/Corvia/django-tenant-users.git" +reference = "master" +resolved_reference = "6a50750e9fd8411e698acbcaa70676a09f141d99" + +[[package]] +name = "django-tenants" +version = "3.3.2" +description = "Tenant support for Django using PostgreSQL schemas." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Django = ">=2.1,<4.0" + [[package]] name = "libsass" version = "0.21.0" @@ -202,7 +228,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "de84127badac66220a787600d18455d7cf562aba0c81c05d7219f39f54a97cf9" +content-hash = "efbcfa2f67a6955f114171ea17e50bf2d5da52108aaf0c335bb29fe87cb40451" [metadata.files] asgiref = [ @@ -215,8 +241,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, ] bleach = [ - {file = "bleach-4.0.0-py2.py3-none-any.whl", hash = "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d"}, - {file = "bleach-4.0.0.tar.gz", hash = "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8"}, + {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, + {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, ] bleach-allowlist = [ {file = "bleach-allowlist-1.0.3.tar.gz", hash = "sha256:56e22086079a0e6a3100ae99e4dbaf20eb2585486598eb0e994008a11428da9b"}, @@ -238,6 +264,10 @@ django-simple-history = [ {file = "django-simple-history-3.0.0.tar.gz", hash = "sha256:66fe76c560054be393c52b1799661e104fbe372918d37d151e5d41c676158118"}, {file = "django_simple_history-3.0.0-py2.py3-none-any.whl", hash = "sha256:a312adfe8fbec4c450b08e641b11249a8a589a7e7d1ba2404764b8b5bed53552"}, ] +django-tenant-users = [] +django-tenants = [ + {file = "django-tenants-3.3.2.tar.gz", hash = "sha256:e3897662bb88007216ef6e3dde4467db394d4f1a4430b10aebc3ec9e58e8e20f"}, +] libsass = [ {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, diff --git a/pyproject.toml b/pyproject.toml index 2f1755c..381507d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,8 @@ Pillow = "^8.3.1" sorl-thumbnail = "^12.7.0" libsass = "^0.21.0" django-debug-toolbar = "^3.2.2" +django-tenants = "^3.3.2" +django-tenant-users = {git = "https://github.com/Corvia/django-tenant-users.git"} [tool.poetry.dev-dependencies] diff --git a/rpg_notes/settings.py b/rpg_notes/settings.py index 9aafa53..e1f4062 100644 --- a/rpg_notes/settings.py +++ b/rpg_notes/settings.py @@ -25,22 +25,57 @@ ALLOWED_HOSTS = [] # Application definition -INSTALLED_APPS = [ - 'notes.apps.NotesConfig', - 'django.contrib.admin', +DATABASE_ROUTERS = ( + 'django_tenants.routers.TenantSyncRouter', +) +SHARED_APPS = ( + 'django_tenants', # mandatory + 'campaigns', # you must list the app where your tenant model resides in + 'users', 'django.contrib.auth', 'django.contrib.contenttypes', + 'tenant_users.permissions', # Defined in both shared apps and tenant apps + 'tenant_users.tenants', # defined only in shared apps 'django.contrib.sessions', 'django.contrib.messages', - 'django.contrib.staticfiles', + 'django.contrib.admin', 'django.contrib.humanize', - 'simple_history', + 'django.contrib.staticfiles', 'django_bootstrap5', 'sorl.thumbnail', 'debug_toolbar' -] +) + +TENANT_APPS = ( + # The following Django contrib apps must be in TENANT_APPS + 'django.contrib.auth', # Defined in both shared apps and tenant apps + 'django.contrib.contenttypes', # Defined in both shared apps and tenant apps + 'tenant_users.permissions', # Defined in both shared apps and tenant apps + 'django.contrib.admin', + + 'notes', + 'simple_history', + +) + +INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS] + +TENANT_MODEL = "campaigns.Campaign" + +TENANT_DOMAIN_MODEL = "campaigns.Domain" + +TENANT_USERS_DOMAIN = "test.localhost" + +AUTH_USER_MODEL = 'users.TenantUser' + +AUTHENTICATION_BACKENDS = ( + 'tenant_users.permissions.backend.UserBackend', +) + +SESSION_COOKIE_DOMAIN = '.test.localhost' MIDDLEWARE = [ + 'django_tenants.middleware.main.TenantMainMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -61,6 +96,7 @@ TEMPLATES = [ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ + 'django.template.context_processors.request', 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 0000000..8ea6271 --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load django_bootstrap5 %} +{% load static %} + + + +{% block mainpage %} +

Login

+ + {% if next %} +
+ You need to log in before you can access + {{ next }}. +
+ {% endif %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + {% url 'admin_password_reset' as password_reset_url %} + + +
+{% endblock %} diff --git a/users/__init__.py b/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/admin.py b/users/admin.py new file mode 100644 index 0000000..cc6a394 --- /dev/null +++ b/users/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +# Register your models here. +from django.contrib.auth.admin import UserAdmin + +from users.models import TenantUser + +admin.site.register(TenantUser) diff --git a/users/apps.py b/users/apps.py new file mode 100644 index 0000000..72b1401 --- /dev/null +++ b/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'users' diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py new file mode 100644 index 0000000..fb83536 --- /dev/null +++ b/users/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.6 on 2021-08-28 21:33 + +from django.db import migrations, models +import tenant_users.permissions.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('campaigns', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='TenantUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email Address')), + ('is_active', models.BooleanField(default=True, verbose_name='active')), + ('is_verified', models.BooleanField(default=False, verbose_name='verified')), + ('name', models.CharField(blank=True, max_length=100, verbose_name='Name')), + ('tenants', models.ManyToManyField(blank=True, help_text='The tenants this user belongs to.', related_name='user_set', to='campaigns.Campaign', verbose_name='tenants')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, tenant_users.permissions.models.PermissionsMixinFacade), + ), + ] diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/models.py b/users/models.py new file mode 100644 index 0000000..ee5e586 --- /dev/null +++ b/users/models.py @@ -0,0 +1,11 @@ +from django.db import models + +from tenant_users.tenants.models import UserProfile + + +class TenantUser(UserProfile): + name = models.CharField( + "Name", + max_length=100, + blank=True, + ) diff --git a/users/tests.py b/users/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/users/views.py b/users/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.