1
0
Fork 0
mirror of https://github.com/Findus23/RPGnotes.git synced 2024-09-19 15:43:45 +02:00

multi-tenant-system

This commit is contained in:
Lukas Winkler 2021-08-29 00:20:02 +02:00
parent 3c884d4e1d
commit dbf5f721ce
Signed by: lukas
GPG key ID: 54DE4D798D244853
42 changed files with 464 additions and 128 deletions

0
campaigns/__init__.py Normal file
View file

9
campaigns/admin.py Normal file
View file

@ -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',)

6
campaigns/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CampaignsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'campaigns'

View file

@ -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,
},
),
]

View file

@ -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),
),
]

View file

24
campaigns/models.py Normal file
View file

@ -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

3
campaigns/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
campaigns/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -2,9 +2,8 @@ from django.contrib import admin
# Register your models here. # Register your models here.
from simple_history.admin import SimpleHistoryAdmin 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(Character, SimpleHistoryAdmin)
admin.site.register(Faction, SimpleHistoryAdmin) admin.site.register(Faction, SimpleHistoryAdmin)
admin.site.register(Location, SimpleHistoryAdmin) admin.site.register(Location, SimpleHistoryAdmin)

View file

@ -1,13 +1,8 @@
from django.forms import ModelForm, ModelMultipleChoiceField, CheckboxSelectMultiple 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 LootForm(ModelForm):
class Meta: class Meta:

View file

@ -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',
),
]

View file

@ -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),
),
]

View file

@ -1,5 +1,4 @@
from .descriptionmodel import DescriptionModel from .descriptionmodel import DescriptionModel
from .campaign import Campaign
from .basemodel import BaseModel from .basemodel import BaseModel
from .faction import Faction from .faction import Faction
from .location import Location from .location import Location

View file

@ -1,17 +1,13 @@
from django.db import models from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from notes.models import Campaign
class BaseModel(models.Model): class BaseModel(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
name = models.CharField(max_length=1000) name = models.CharField(max_length=1000)
slug = models.SlugField(editable=False) slug = models.SlugField(editable=False, unique=True)
class Meta: class Meta:
abstract = True abstract = True
unique_together = ["campaign", "slug"]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.id: if not self.id:

View file

@ -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

View file

@ -24,7 +24,7 @@ class Character(BaseModel, DescriptionModel):
ordering = ["name"] ordering = ["name"]
def get_absolute_url(self): def get_absolute_url(self):
return reverse('characterdetail', args=[self.campaign.slug, self.slug]) return reverse('characterdetail', args=[self.slug])
def initials(self): def initials(self):
return "".join([word[0] for word in self.name.split()][:2]).upper() return "".join([word[0] for word in self.name.split()][:2]).upper()

View file

@ -3,12 +3,10 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from notes.models import Campaign, Session, DescriptionModel from notes.models import Session, DescriptionModel
class IngameDay(DescriptionModel): class IngameDay(DescriptionModel):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
day = models.PositiveIntegerField() day = models.PositiveIntegerField()
sessions = models.ManyToManyField(Session, related_name="ingame_days") sessions = models.ManyToManyField(Session, related_name="ingame_days")
@ -18,10 +16,9 @@ class IngameDay(DescriptionModel):
class Meta: class Meta:
ordering = ["-day"] ordering = ["-day"]
unique_together = ["campaign", "day"]
def get_absolute_url(self): def get_absolute_url(self):
return reverse('daydetail', args=[self.campaign.slug, self.day]) return reverse('daydetail', args=[self.day])
@property @property
def prettyname(self): def prettyname(self):

View file

@ -2,12 +2,10 @@ from django.conf import settings
from django.db import models from django.db import models
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from notes.models import Campaign, DescriptionModel from notes.models import DescriptionModel
class Loot(DescriptionModel): class Loot(DescriptionModel):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
name = models.CharField(max_length=1000) name = models.CharField(max_length=1000)
quantity = models.PositiveSmallIntegerField() quantity = models.PositiveSmallIntegerField()
value_gold = models.DecimalField("Value (Gold)", max_digits=7, decimal_places=2) value_gold = models.DecimalField("Value (Gold)", max_digits=7, decimal_places=2)

View file

@ -4,12 +4,8 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from notes.models import Campaign
class Session(models.Model): class Session(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
day = models.DateField(default=date.today) day = models.DateField(default=date.today)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True) last_modified = models.DateTimeField(auto_now=True)
@ -17,10 +13,9 @@ class Session(models.Model):
class Meta: class Meta:
ordering = ["-day"] ordering = ["-day"]
unique_together = ["campaign", "day"]
def get_absolute_url(self): def get_absolute_url(self):
return reverse('sessiondetail', args=[self.campaign.slug, self.id]) return reverse('sessiondetail', args=[self.id])
def __str__(self): def __str__(self):
return str(self.day) return str(self.day)

View file

@ -9,17 +9,17 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav">
{# <li class="nav-item">#}
{# <a class="nav-link active" href="{% url "campaigndetail" view.kwargs.campslug %}">Home</a>#}
{# </li>#}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="{% url "campaigndetail" view.kwargs.campslug %}">Home</a> <a class="nav-link" href="{% url "characterlist" %}">Characters</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url "characterlist" view.kwargs.campslug %}">Characters</a> <a class="nav-link" href="{% url "daylist" %}">Timeline</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url "daylist" view.kwargs.campslug %}">Timeline</a> <a class="nav-link" href="{% url "lootlist" %}">Loot</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "lootlist" view.kwargs.campslug %}">Loot</a>
</li> </li>
</ul> </ul>
</div> </div>

View file

@ -16,7 +16,7 @@
{% include "notes/macros/character-pillar.html" with character=c %} {% include "notes/macros/character-pillar.html" with character=c %}
{% endfor %} {% endfor %}
</ul> </ul>
<a class="btn btn-primary" href="{% url "characteradd" character.campaign.slug %}">Add Character</a> <a class="btn btn-primary" href="{% url "characteradd" %}">Add Character</a>
</div> </div>
<div class="col-8"> <div class="col-8">
<div class="character-heading" style="border-bottom-color: #{{ character.color }}"> <div class="character-heading" style="border-bottom-color: #{{ character.color }}">
@ -35,7 +35,7 @@
<h1> <h1>
{{ character.name }} {{ character.name }}
<a href="{% url "characteredit" character.campaign.slug character.slug %}"> <a href="{% url "characteredit" character.slug %}">
edit edit
</a> </a>
</h1> </h1>

View file

@ -6,7 +6,7 @@
{% if edit %} {% if edit %}
<h1>Edit "{{ object.name }}"</h1> <h1>Edit "{{ object.name }}"</h1>
{% if request.resolver_match.view_name %} {% if request.resolver_match.view_name %}
<a class="btn btn-danger" href="{% url "lootdelete" object.campaign.slug object.id %}">Delete</a> <a class="btn btn-danger" href="{% url "lootdelete" object.id %}">Delete</a>
{% endif %} {% endif %}
{% else %} {% else %}
<h1>Add new</h1> <h1>Add new</h1>

View file

@ -34,7 +34,7 @@
<tr> <tr>
<td colspan="5" class="collapse-cell"> <td colspan="5" class="collapse-cell">
<div class="collapse" id="row-{{ l.id }}"> <div class="collapse" id="row-{{ l.id }}">
<h3>{{ l.name }} <a href="{% url "lootedit" l.campaign.slug l.id %}">edit</a></h3> <h3>{{ l.name }} <a href="{% url "lootedit" l.id %}">edit</a></h3>
{{ l.description_html|safe }} {{ l.description_html|safe }}
<p>Value each: {{ l.value_per_unit|format_money_html }}</p> <p>Value each: {{ l.value_per_unit|format_money_html }}</p>
</div> </div>
@ -47,7 +47,7 @@
<dd>Total value:</dd> <dd>Total value:</dd>
<dt>{{ total_value|format_money_html }}</dt> <dt>{{ total_value|format_money_html }}</dt>
</dl> </dl>
<a href="{% url "lootadd" loot.0.campaign.slug %}" class="btn btn-primary">Add Loot</a> <a href="{% url "lootadd" %}" class="btn btn-primary">Add Loot</a>
{% endblock %} {% endblock %}

View file

@ -3,23 +3,23 @@ from django.urls import path
from notes import views from notes import views
urlpatterns = [ urlpatterns = [
path("", views.CampaignListView.as_view(), name="campaignlist"), # path("", views.CampaignListView.as_view(), name="campaignlist"),
path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"), # path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"),
path("c/<slug:campslug>", views.CampaignDetailView.as_view(), name="campaigndetail"), # path("c/<slug:campslug>", views.CampaignDetailView.as_view(), name="campaigndetail"),
path("c/<slug:campslug>/edit", views.CampaignEditView.as_view(), name="campaignedit"), # path("c/<slug:campslug>/edit", views.CampaignEditView.as_view(), name="campaignedit"),
path("c/<slug:campslug>/delete", views.CampaignDeleteView.as_view(), name="campaigndelete"), # path("c/<slug:campslug>/delete", views.CampaignDeleteView.as_view(), name="campaigndelete"),
path("c/<slug:campslug>/loot", views.LootListView.as_view(), name="lootlist"), path("loot", views.LootListView.as_view(), name="lootlist"),
path("c/<slug:campslug>/loot/<int:pk>/edit", views.LootEditView.as_view(), name="lootedit"), path("loot/<int:pk>/edit", views.LootEditView.as_view(), name="lootedit"),
path("c/<slug:campslug>/loot/<int:pk>/delete", views.LootDeleteView.as_view(), name="lootdelete"), path("loot/<int:pk>/delete", views.LootDeleteView.as_view(), name="lootdelete"),
path("c/<slug:campslug>/loot/add", views.LootCreateView.as_view(), name="lootadd"), path("loot/add", views.LootCreateView.as_view(), name="lootadd"),
path("c/<slug:campslug>/character/", views.list_character_redirect, name="characterlist"), path("character/", views.list_character_redirect, name="characterlist"),
path("c/<slug:campslug>/character/add", views.CharacterCreateView.as_view(), name="characteradd"), path("character/add", views.CharacterCreateView.as_view(), name="characteradd"),
path("c/<slug:campslug>/character/<slug:charslug>", views.CharacterDetailView.as_view(), name="characterdetail"), path("character/<slug:slug>", views.CharacterDetailView.as_view(), name="characterdetail"),
path("c/<slug:campslug>/character/<slug:charslug>/edit", views.CharacterEditView.as_view(), name="characteredit"), path("character/<slug:slug>/edit", views.CharacterEditView.as_view(), name="characteredit"),
path("c/<slug:campslug>/character/<slug:charslug>/delete", views.CharacterDeleteView.as_view(), name="characterdelete"), path("character/<slug:slug>/delete", views.CharacterDeleteView.as_view(), name="characterdelete"),
path("c/<slug:campslug>/day/", views.list_day_redirect, name="daylist"), path("day/", views.list_day_redirect, name="daylist"),
path("c/<slug:campslug>/day/add", views.DayCreateView.as_view(), name="dayadd"), path("day/add", views.DayCreateView.as_view(), name="dayadd"),
path("c/<slug:campslug>/day/<int:day>", views.DayDetailView.as_view(), name="daydetail"), path("day/<int:day>", views.DayDetailView.as_view(), name="daydetail"),
path("c/<slug:campslug>/day/<int:day>/edit", views.DayEditView.as_view(), name="dayedit"), path("day/<int:day>/edit", views.DayEditView.as_view(), name="dayedit"),
path("c/<slug:campslug>/day/<int:day>/delete", views.DayDeleteView.as_view(), name="daydelete"), path("day/<int:day>/delete", views.DayDeleteView.as_view(), name="daydelete"),
] ]

View file

@ -13,7 +13,8 @@ currencies_HTML = [
"<span class='pp'>PP</span>" "<span class='pp'>PP</span>"
] ]
use_platinum=False use_platinum = False
def digitize(n): def digitize(n):
print("n", n) print("n", n)
@ -33,6 +34,8 @@ def digitize(n):
def format_money(money: Decimal, html=False) -> str: def format_money(money: Decimal, html=False) -> str:
if not money:
return ""
currencies = currencies_HTML if html else currencies_text currencies = currencies_HTML if html else currencies_text
output = [] output = []
cp = round(money / copper, ndigits=0) cp = round(money / copper, ndigits=0)

View file

@ -1,4 +1,4 @@
from .campaign_views import * # from .campaign_views import *
from .loot_views import * from .loot_views import *
from .character_views import * from .character_views import *
from .day_views import * from .day_views import *

View file

@ -3,7 +3,7 @@ from django.urls import reverse_lazy
from django.views import generic from django.views import generic
from notes.forms import CharacterForm from notes.forms import CharacterForm
from notes.models import Character, Campaign from notes.models import Character
# class CharacterListView(generic.ListView): # class CharacterListView(generic.ListView):
@ -15,8 +15,9 @@ from notes.models import Character, Campaign
# #
def list_character_redirect(request, *args, **kwargs): 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) return redirect(first_character)
@ -28,18 +29,17 @@ class CharacterDetailView(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
data["player_characters"] = Character.objects.filter( data["player_characters"] = Character.objects.filter(
campaign__slug=self.kwargs['campslug'],
player__isnull=False player__isnull=False
).select_related() ).select_related()
data["npcs"] = Character.objects.filter( data["npcs"] = Character.objects.filter(
campaign__slug=self.kwargs['campslug'], player__isnull=True player__isnull=True
).select_related() ).select_related()
return data return data
def get_object(self, queryset=None): # def get_object(self, queryset=None):
return Character.objects.get( # return Character.objects.get(
campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug'] # campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']
) # )
class CharacterCreateView(generic.CreateView): class CharacterCreateView(generic.CreateView):
@ -48,18 +48,14 @@ class CharacterCreateView(generic.CreateView):
form_class = CharacterForm form_class = CharacterForm
context_object_name = "object" 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): class CharacterEditView(generic.UpdateView):
template_name = "notes/loot_edit.html" template_name = "notes/loot_edit.html"
model = Character model = Character
form_class = CharacterForm form_class = CharacterForm
def get_object(self, queryset=None): # def get_object(self, queryset=None):
return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']) # return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug'])
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)

View file

@ -3,11 +3,13 @@ from django.urls import reverse_lazy
from django.views import generic from django.views import generic
from notes.forms import DayForm from notes.forms import DayForm
from notes.models import Session, Campaign, IngameDay from notes.models import IngameDay
def list_day_redirect(request, *args, **kwargs): def list_day_redirect(request, *args, **kwargs):
latest_day: IngameDay = IngameDay.objects.first() latest_day: IngameDay = IngameDay.objects.first()
if not latest_day:
return redirect("dayadd")
return redirect(latest_day) return redirect(latest_day)
@ -31,10 +33,6 @@ class DayCreateView(generic.CreateView):
form_class = DayForm form_class = DayForm
context_object_name = "object" 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): class DayEditView(generic.UpdateView):
template_name = "notes/loot_edit.html" template_name = "notes/loot_edit.html"
@ -46,13 +44,14 @@ class DayEditView(generic.UpdateView):
data['edit'] = True data['edit'] = True
return data return data
def get_object(self, queryset=None): # def get_object(self, queryset=None):
return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) # return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day'])
class DayDeleteView(generic.DeleteView): class DayDeleteView(generic.DeleteView):
template_name = "notes/campaign_confirm_delete.html" template_name = "notes/campaign_confirm_delete.html"
model = IngameDay model = IngameDay
success_url = reverse_lazy('daylist') success_url = reverse_lazy('daylist')
def get_object(self, queryset=None): # def get_object(self, queryset=None):
return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day']) # return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day'])

View file

@ -3,7 +3,7 @@ from django.urls import reverse_lazy, reverse
from django.views import generic from django.views import generic
from notes.forms import LootForm from notes.forms import LootForm
from notes.models import Loot, Campaign from notes.models import Loot
class LootListView(generic.ListView): class LootListView(generic.ListView):
@ -11,8 +11,8 @@ class LootListView(generic.ListView):
model = Loot model = Loot
context_object_name = "loot" context_object_name = "loot"
def get_queryset(self): # def get_queryset(self):
return Loot.objects.filter(campaign__slug=self.kwargs['campslug']) # return Loot.objects.filter(campaign__slug=self.kwargs['campslug'])
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
@ -31,12 +31,8 @@ class LootCreateView(generic.CreateView):
model = Loot model = Loot
form_class = LootForm form_class = LootForm
def form_valid(self, form): success_url = reverse_lazy("lootlist")
form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug'])
return super().form_valid(form)
def get_success_url(self):
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")})
class LootEditView(generic.UpdateView): class LootEditView(generic.UpdateView):
@ -45,8 +41,7 @@ class LootEditView(generic.UpdateView):
form_class = LootForm form_class = LootForm
context_object_name = "object" context_object_name = "object"
def get_success_url(self): success_url = reverse_lazy("lootlist")
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")})
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
@ -57,5 +52,5 @@ class LootEditView(generic.UpdateView):
class LootDeleteView(generic.DeleteView): class LootDeleteView(generic.DeleteView):
template_name = "notes/campaign_confirm_delete.html" template_name = "notes/campaign_confirm_delete.html"
model = Loot model = Loot
def get_success_url(self):
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")}) success_url = reverse_lazy("lootlist")

38
poetry.lock generated
View file

@ -26,7 +26,7 @@ lxml = ["lxml"]
[[package]] [[package]]
name = "bleach" name = "bleach"
version = "4.0.0" version = "4.1.0"
description = "An easy safelist-based HTML-sanitizing tool." description = "An easy safelist-based HTML-sanitizing tool."
category = "main" category = "main"
optional = false optional = false
@ -94,6 +94,32 @@ category = "main"
optional = false optional = false
python-versions = ">=3.6" 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]] [[package]]
name = "libsass" name = "libsass"
version = "0.21.0" version = "0.21.0"
@ -202,7 +228,7 @@ python-versions = "*"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "de84127badac66220a787600d18455d7cf562aba0c81c05d7219f39f54a97cf9" content-hash = "efbcfa2f67a6955f114171ea17e50bf2d5da52108aaf0c335bb29fe87cb40451"
[metadata.files] [metadata.files]
asgiref = [ asgiref = [
@ -215,8 +241,8 @@ beautifulsoup4 = [
{file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
] ]
bleach = [ bleach = [
{file = "bleach-4.0.0-py2.py3-none-any.whl", hash = "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d"}, {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"},
{file = "bleach-4.0.0.tar.gz", hash = "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8"}, {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"},
] ]
bleach-allowlist = [ bleach-allowlist = [
{file = "bleach-allowlist-1.0.3.tar.gz", hash = "sha256:56e22086079a0e6a3100ae99e4dbaf20eb2585486598eb0e994008a11428da9b"}, {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.tar.gz", hash = "sha256:66fe76c560054be393c52b1799661e104fbe372918d37d151e5d41c676158118"},
{file = "django_simple_history-3.0.0-py2.py3-none-any.whl", hash = "sha256:a312adfe8fbec4c450b08e641b11249a8a589a7e7d1ba2404764b8b5bed53552"}, {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 = [ libsass = [
{file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, {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"}, {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"},

View file

@ -17,6 +17,8 @@ Pillow = "^8.3.1"
sorl-thumbnail = "^12.7.0" sorl-thumbnail = "^12.7.0"
libsass = "^0.21.0" libsass = "^0.21.0"
django-debug-toolbar = "^3.2.2" 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] [tool.poetry.dev-dependencies]

View file

@ -25,22 +25,57 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ DATABASE_ROUTERS = (
'notes.apps.NotesConfig', 'django_tenants.routers.TenantSyncRouter',
'django.contrib.admin', )
SHARED_APPS = (
'django_tenants', # mandatory
'campaigns', # you must list the app where your tenant model resides in
'users',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', '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.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.admin',
'django.contrib.humanize', 'django.contrib.humanize',
'simple_history', 'django.contrib.staticfiles',
'django_bootstrap5', 'django_bootstrap5',
'sorl.thumbnail', 'sorl.thumbnail',
'debug_toolbar' '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 = [ MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@ -61,6 +96,7 @@ TEMPLATES = [
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.request',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',

View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% load django_bootstrap5 %}
{% load static %}
{% block mainpage %}
<h1>Login</h1>
{% if next %}
<div class="alert alert-info">
You need to log in before you can access
<code>{{ next }}</code>.
</div>
{% endif %}
<form method="post" class="form">
{% csrf_token %}
{% bootstrap_form form %}
{% url 'admin_password_reset' as password_reset_url %}
<div class="password-reset-link">
<a href="{% url 'password_reset' %}">Forgotten your password or username?</a>
</div>
<button type="submit" class="btn btn-lg btn-primary btn-block">
Sign in
</button>
</form>
{% endblock %}

0
users/__init__.py Normal file
View file

8
users/admin.py Normal file
View file

@ -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)

6
users/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'users'

View file

@ -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),
),
]

View file

11
users/models.py Normal file
View file

@ -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,
)

3
users/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
users/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.