Add Profile.
This commit is contained in:
parent
fad08758ce
commit
0bf64182f5
10 changed files with 211 additions and 9 deletions
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path
|
||||
from django.urls import path, reverse_lazy
|
||||
|
||||
from . import views
|
||||
|
||||
|
@ -8,7 +8,7 @@ urlpatterns = [
|
|||
# https://docs.djangoproject.com/en/2.2/topics/auth/default/#module-django.contrib.auth.views
|
||||
path("login/", auth_views.LoginView.as_view(), name="login"),
|
||||
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||
path("password_change/", auth_views.PasswordChangeView.as_view(success_url="/"), name="password_change"),
|
||||
path("password_change/", auth_views.PasswordChangeView.as_view(success_url=reverse_lazy("user_profile")), name="password_change"),
|
||||
|
||||
path("signup/", views.SignupView.as_view(), name="signup"),
|
||||
]
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_authenticated %}
|
||||
<span class="navbar-text">Logged in as <strong>{{ user.username }}</strong></span>
|
||||
<span class="navbar-text">Logged in as <a class="font-weight-bold" href="{% url 'user_profile' %}">{{ user.username }}</a></span>
|
||||
<a class="btn btn-outline-danger ml-3" role="button" href="{% url 'logout' %}">Log Out</a>
|
||||
{% else %}
|
||||
<a class="btn btn-outline-info mr-1" role="button" href="{% url 'signup' %}">Sign Up</a>
|
||||
|
|
63
drakul/base/templates/bootstrap4/table_inline_formset.html
Normal file
63
drakul/base/templates/bootstrap4/table_inline_formset.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
{% comment %}
|
||||
Based on Crispy's default bootstrap4/table_inline_formset.html, but removed 'table-striped'
|
||||
https://github.com/django-crispy-forms/django-crispy-forms/blob/master/crispy_forms/templates/bootstrap4/table_inline_formset.html
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
{% load crispy_forms_utils %}
|
||||
{% load crispy_forms_field %}
|
||||
|
||||
{% specialspaceless %}
|
||||
{% if formset_tag %}
|
||||
<form {{ flat_attrs|safe }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{% endif %}
|
||||
{% if formset_method|lower == 'post' and not disable_csrf %}
|
||||
{% csrf_token %}
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{{ formset.management_form|crispy }}
|
||||
</div>
|
||||
|
||||
<table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-sm">
|
||||
<thead>
|
||||
{% if formset.readonly and not formset.queryset.exists %}
|
||||
{% else %}
|
||||
<tr>
|
||||
{% for field in formset.forms.0 %}
|
||||
{% if field.label and not field.is_hidden %}
|
||||
<th for="{{ field.auto_id }}" class="col-form-label {% if field.field.required %}requiredField{% endif %}">
|
||||
{{ field.label|safe }}{% if field.field.required and not field|is_checkbox %}<span class="asteriskField">*</span>{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr class="d-none empty-form">
|
||||
{% for field in formset.empty_form %}
|
||||
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
{% for form in formset %}
|
||||
{% if form_show_errors and not form.is_extra %}
|
||||
{% include "bootstrap4/errors.html" %}
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
{% for field in form %}
|
||||
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% include "bootstrap4/inputs.html" %}
|
||||
|
||||
{% if formset_tag %}</form>{% endif %}
|
||||
{% endspecialspaceless %}
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
||||
|
||||
from .forms import UserCharactersInlineFormSet
|
||||
from .models import User, Character, Rank
|
||||
|
||||
|
||||
|
@ -12,6 +13,7 @@ class RankAdmin(admin.ModelAdmin):
|
|||
|
||||
class CharacterInline(admin.TabularInline):
|
||||
model = Character
|
||||
formset = UserCharactersInlineFormSet # disallows deleting user's main character
|
||||
fields = ["name", "klass", "role"]
|
||||
extra = 0
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
from django.forms import ModelForm
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Field
|
||||
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
||||
from django.forms import ModelForm, inlineformset_factory, BaseInlineFormSet
|
||||
|
||||
from .models import Character
|
||||
from ..users.models import User
|
||||
|
||||
|
||||
class CharacterForm(ModelForm):
|
||||
|
@ -8,3 +12,51 @@ class CharacterForm(ModelForm):
|
|||
model = Character
|
||||
fields = ["name", "klass", "role"]
|
||||
|
||||
|
||||
class UserForm(ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["username", "rank", "main"]
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["rank"].disabled = True
|
||||
self.fields["main"].queryset = user.characters
|
||||
|
||||
helper = FormHelper()
|
||||
helper.layout = Layout(
|
||||
Field("username"),
|
||||
Field("rank"),
|
||||
Field("main"),
|
||||
)
|
||||
helper.render_hidden_fields = True # Include MultiFormMixin's hidden form_id_field_prefix
|
||||
helper.form_tag = False
|
||||
|
||||
|
||||
class UserCharactersInlineFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super().clean()
|
||||
for form in self.deleted_forms:
|
||||
if form.instance == self.instance.main:
|
||||
form._errors[NON_FIELD_ERRORS] = self.error_class(["Can't delete user's main character."])
|
||||
raise ValidationError(["Can't delete user's main character."])
|
||||
|
||||
|
||||
UserCharactersFormSet = inlineformset_factory(
|
||||
User, # parent model
|
||||
Character,
|
||||
formset=UserCharactersInlineFormSet,
|
||||
fields=["name", "klass", "role"],
|
||||
can_delete=True,
|
||||
extra=1
|
||||
)
|
||||
|
||||
|
||||
class UserCharactersFormSetHelper(FormHelper):
|
||||
template = "bootstrap4/table_inline_formset.html"
|
||||
layout = Layout(
|
||||
Field("name"),
|
||||
Field("klass"),
|
||||
Field("role")
|
||||
)
|
||||
form_tag = False
|
||||
|
|
19
drakul/users/migrations/0006_auto_20200924_2019.py
Normal file
19
drakul/users/migrations/0006_auto_20200924_2019.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-24 20:19
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_auto_20191121_1646'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='main',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='users.character'),
|
||||
),
|
||||
]
|
|
@ -33,8 +33,7 @@ class User(AbstractUser):
|
|||
main = models.OneToOneField(
|
||||
"Character",
|
||||
related_name="+",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True
|
||||
on_delete=models.CASCADE # has to cascade to allow User deletion, since 'main' Character is circularly related
|
||||
)
|
||||
|
||||
rank = models.ForeignKey(
|
||||
|
|
30
drakul/users/templates/users/profile.html
Normal file
30
drakul/users/templates/users/profile.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Profile{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header">Profile</h5>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{% url 'user_profile' %}">
|
||||
{% crispy user_form %}
|
||||
<div class="d-flex justify-content-end">
|
||||
<a class="btn btn-outline-info float-right mr-2" role="button" href="{% url 'password_change' %}">Change Password</a>
|
||||
<input class="btn btn-primary float-right" type="submit" name="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
<div class="my-4"></div>
|
||||
<h5>Characters</h5>
|
||||
<hr>
|
||||
<form method="post" class="table-borderless" action="{% url 'user_profile' %}">
|
||||
{% crispy user_characters_formset user_characters_formset_helper %}
|
||||
<input class="btn btn-primary float-right" type="submit" name="submit" value="Save">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -4,4 +4,5 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
path("attendance/", views.AttendanceView.as_view(), name="attendance"),
|
||||
path("profile/", views.UserProfileView.as_view(), name="user_profile"),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
from contextlib import suppress
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Avg, Q, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from .forms import UserForm, UserCharactersFormSet, UserCharactersFormSetHelper
|
||||
from .models import User
|
||||
from ..base.views import MultiModelFormView
|
||||
from ..raids.models import Raid, RaidResponse
|
||||
|
||||
|
||||
|
@ -47,6 +52,37 @@ class AttendanceView(TemplateView):
|
|||
return context
|
||||
|
||||
|
||||
# CharacterDetailView:
|
||||
#slug_field = "title"
|
||||
#slug_url_kwarg = "title"
|
||||
class UserProfileView(LoginRequiredMixin, SingleObjectMixin, MultiModelFormView):
|
||||
template_name = "users/profile.html"
|
||||
form_classes = {
|
||||
"user_form": UserForm,
|
||||
"user_characters_formset": UserCharactersFormSet
|
||||
}
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
def get_user_form_instance(self):
|
||||
return self.object
|
||||
|
||||
def get_user_characters_formset_instance(self):
|
||||
return self.object
|
||||
|
||||
def get_user_form_kwargs(self, kwargs):
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs["user_characters_formset_helper"] = UserCharactersFormSetHelper()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("user_profile")
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
|
Loading…
Reference in a new issue