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

View file

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

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 .campaign import Campaign
from .basemodel import BaseModel
from .faction import Faction
from .location import Location

View file

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

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"]
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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
{% if edit %}
<h1>Edit "{{ object.name }}"</h1>
{% 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 %}
{% else %}
<h1>Add new</h1>

View file

@ -34,7 +34,7 @@
<tr>
<td colspan="5" class="collapse-cell">
<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 }}
<p>Value each: {{ l.value_per_unit|format_money_html }}</p>
</div>
@ -47,7 +47,7 @@
<dd>Total value:</dd>
<dt>{{ total_value|format_money_html }}</dt>
</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 %}

View file

@ -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/<slug:campslug>", views.CampaignDetailView.as_view(), name="campaigndetail"),
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>/loot", views.LootListView.as_view(), name="lootlist"),
path("c/<slug:campslug>/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("c/<slug:campslug>/loot/add", views.LootCreateView.as_view(), name="lootadd"),
path("c/<slug:campslug>/character/", views.list_character_redirect, name="characterlist"),
path("c/<slug:campslug>/character/add", views.CharacterCreateView.as_view(), name="characteradd"),
path("c/<slug:campslug>/character/<slug:charslug>", views.CharacterDetailView.as_view(), name="characterdetail"),
path("c/<slug:campslug>/character/<slug:charslug>/edit", views.CharacterEditView.as_view(), name="characteredit"),
path("c/<slug:campslug>/character/<slug:charslug>/delete", views.CharacterDeleteView.as_view(), name="characterdelete"),
path("c/<slug:campslug>/day/", views.list_day_redirect, name="daylist"),
path("c/<slug:campslug>/day/add", views.DayCreateView.as_view(), name="dayadd"),
path("c/<slug:campslug>/day/<int:day>", views.DayDetailView.as_view(), name="daydetail"),
path("c/<slug:campslug>/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("", views.CampaignListView.as_view(), name="campaignlist"),
# path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"),
# 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>/delete", views.CampaignDeleteView.as_view(), name="campaigndelete"),
path("loot", views.LootListView.as_view(), name="lootlist"),
path("loot/<int:pk>/edit", views.LootEditView.as_view(), name="lootedit"),
path("loot/<int:pk>/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/<slug:slug>", views.CharacterDetailView.as_view(), name="characterdetail"),
path("character/<slug:slug>/edit", views.CharacterEditView.as_view(), name="characteredit"),
path("character/<slug:slug>/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/<int:day>", views.DayDetailView.as_view(), name="daydetail"),
path("day/<int:day>/edit", views.DayEditView.as_view(), name="dayedit"),
path("day/<int:day>/delete", views.DayDeleteView.as_view(), name="daydelete"),
]

View file

@ -13,7 +13,8 @@ currencies_HTML = [
"<span class='pp'>PP</span>"
]
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)

View file

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

View file

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

View file

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

View file

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

38
poetry.lock generated
View file

@ -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"},

View file

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

View file

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

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.