Added my own base FormView, and replaced the current function-based user form views with a new UserFormView that based on my base FormView in the Mia core application. I do not know if I am doing the right thing.

This commit is contained in:
依瑪貓 2020-08-13 15:43:04 +08:00
parent e06821194c
commit 3c655b8f87
3 changed files with 210 additions and 73 deletions

View File

@ -24,6 +24,7 @@ from dirtyfields import DirtyFieldsMixin
from django.conf import settings from django.conf import settings
from django.db import models, connection, OperationalError, transaction from django.db import models, connection, OperationalError, transaction
from django.db.models.functions import Now from django.db.models.functions import Now
from django.urls import reverse
from mia_core.utils import get_multi_lingual_attr, set_multi_lingual_attr, \ from mia_core.utils import get_multi_lingual_attr, set_multi_lingual_attr, \
new_pk new_pk
@ -100,6 +101,10 @@ class User(DirtyFieldsMixin, models.Model):
REQUIRED_FIELDS = ["id", "name"] REQUIRED_FIELDS = ["id", "name"]
USERNAME_FIELD = "login_id" USERNAME_FIELD = "login_id"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current_user = None
@property @property
def is_anonymous(self) -> bool: def is_anonymous(self) -> bool:
return False return False
@ -119,20 +124,23 @@ class User(DirtyFieldsMixin, models.Model):
return "%s (%s)" % ( return "%s (%s)" % (
self.name.__str__(), self.login_id.__str__()) self.name.__str__(), self.login_id.__str__())
def save(self, current_user=None, force_insert=False, force_update=False, def save(self, force_insert=False, force_update=False, using=None,
using=None, update_fields=None): update_fields=None):
if self.pk is None: if self.pk is None:
self.pk = new_pk(User) self.pk = new_pk(User)
if current_user is not None: if self.current_user is not None:
self.created_by = current_user self.created_by = self.current_user
if current_user is not None: if self.current_user is not None:
self.updated_by = current_user self.updated_by = self.current_user
with transaction.atomic(): with transaction.atomic():
super(User, self).save( super(User, self).save(
force_insert=force_insert, force_update=force_update, force_insert=force_insert, force_update=force_update,
using=using, update_fields=update_fields) using=using, update_fields=update_fields)
User.objects.filter(pk=self.pk).update(updated_at=Now()) User.objects.filter(pk=self.pk).update(updated_at=Now())
def get_absolute_url(self):
return reverse("mia_core:users.detail", args=(self,))
class Meta: class Meta:
db_table = "users" db_table = "users"
app_label = "mia_core" app_label = "mia_core"

View File

@ -31,11 +31,11 @@ app_name = "mia_core"
urlpatterns = [ urlpatterns = [
path("logout", views.logout, name="logout"), path("logout", views.logout, name="logout"),
path("users", views.UserListView.as_view(), name="users"), path("users", views.UserListView.as_view(), name="users"),
path("users/create", views.user_form, name="users.create"), path("users/create", views.UserFormView.as_view(), name="users.create"),
path("users/store", views.user_store, name="users.store"), path("users/store", views.UserFormView.as_view(), name="users.store"),
path("users/<user:user>", views.UserView.as_view(), name="users.detail"), path("users/<user:user>", views.UserView.as_view(), name="users.detail"),
path("users/<user:user>/edit", views.user_form, name="users.edit"), path("users/<user:user>/edit", views.UserFormView.as_view(), name="users.edit"),
path("users/<user:user>/update", views.user_store, name="users.update"), path("users/<user:user>/update", views.UserFormView.as_view(), name="users.update"),
path("users/<user:user>/delete", views.user_delete, name="users.delete"), path("users/<user:user>/delete", views.user_delete, name="users.delete"),
path("api/users/<str:login_id>/exists", views.api_users_exists, path("api/users/<str:login_id>/exists", views.api_users_exists,
name="api.users.exists"), name="api.users.exists"),

View File

@ -18,11 +18,16 @@
"""The views of the Mia core application. """The views of the Mia core application.
""" """
from typing import Dict, Type, Optional, Union
from dirtyfields import DirtyFieldsMixin
from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import logout as logout_user from django.contrib.auth import logout as logout_user
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Model
from django.http import HttpResponse, JsonResponse, HttpRequest, \ from django.http import HttpResponse, JsonResponse, HttpRequest, \
HttpResponseRedirect HttpResponseRedirect, Http404
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -30,12 +35,157 @@ from django.utils.translation import gettext_noop
from django.views.decorators.http import require_POST, require_GET from django.views.decorators.http import require_POST, require_GET
from django.views.generic import DeleteView as CoreDeleteView, ListView, \ from django.views.generic import DeleteView as CoreDeleteView, ListView, \
DetailView DetailView
from django.views.generic.base import View
from . import stored_post from . import stored_post, utils
from .digest_auth import login_required from .digest_auth import login_required
from .forms import UserForm from .forms import UserForm
from .models import User from .models import User
from .utils import strip_post from .utils import strip_post, UrlBuilder
class FormView(View):
"""The base form view."""
model: Type[Model] = None
form: Type[forms.Form] = None
template_name: str = None
context_object_name: str = "form"
error_url: str = None
success_url: str = None
not_modified_message: str = None
success_message: str = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._object = None
self._is_object_requested = False
def dispatch(self, request: HttpRequest, *args, **kwargs):
"""The view to store an accounting transaction.
Returns:
The response.
"""
obj = self.get_current_object()
if self.request.method != "POST":
previous_post = stored_post.get_previous_post(self.request)
if previous_post is not None:
form = self.make_form_from_post(previous_post)
elif obj is not None:
form = self.make_form_from_model(obj)
else:
form = self._form()
return render(self.request, self.get_template_name(), {
self.context_object_name: form
})
else:
post = self.request.POST.dict()
utils.strip_post(post)
form = self.make_form_from_post(post)
if not form.is_valid():
url = str(utils.UrlBuilder(self.get_error_url())
.query(r=self.request.GET.get("r")))
return stored_post.error_redirect(request, url, post)
if obj is None:
obj = self._model()
self._set_current_object(obj)
self.fill_model_from_form(obj, form)
if isinstance(obj, DirtyFieldsMixin)\
and not obj.is_dirty(check_relationship=True):
message = self.get_not_modified_message()
else:
obj.save()
message = self.get_success_message()
messages.success(request, message)
return redirect(str(UrlBuilder(self.get_success_url())
.query(r=self.request.GET.get("r"))))
@property
def _form(self):
if self.form is None:
raise AttributeError("The form attribute was not set.")
return self.form
@property
def _model(self):
if self.model is None:
raise AttributeError("The model attribute was not set.")
return self.model
def _set_current_object(self, obj: Model) -> None:
"""Sets the current object that we are operating."""
self._object = obj
self._is_object_requested = True
def _get_current_object(self) -> Optional[Model]:
"""Returns the current object that we are operating and cached."""
if not self._is_object_requested:
self._object = self.get_current_object()
self._is_object_requested = True
return self._object
def get_template_name(self) -> str:
"""Returns the name of the template."""
if self.template_name is not None:
return self.template_name
if self.model is not None:
app_name = self.request.resolver_match.app_name
model_name = self.model.__name__.lower()
return F"{app_name}/{model_name}_form.html"
raise AttributeError(
"Please either define the template_name or the model attribute.")
def make_form_from_post(self, post: Dict[str, str]) -> forms.Form:
"""Creates and returns the form from the POST data."""
return self._form(post)
def make_form_from_model(self, obj: Model) -> forms.Form:
"""Creates and returns the form from a data model."""
return self._form(obj)
def fill_model_from_form(self, obj: Model, form: forms.Form) -> None:
"""Fills in the data model from the form."""
for name in form.data.keys():
setattr(obj, name, form.data[name])
def get_error_url(self) -> str:
"""Returns the URL on error."""
if self.error_url is not None:
return self.error_url
raise AttributeError(
"Please define either the error_url attribute"
" or the get_error_url method.")
def get_success_url(self) -> str:
"""Returns the URL on success."""
if self.success_url is not None:
return self.success_url
obj = self._get_current_object()
get_absolute_url = getattr(obj, "get_absolute_url", None)
if get_absolute_url is not None:
return get_absolute_url()
raise AttributeError(
"Please define either the success_url attribute,"
" the get_absolute_url method on the model,"
" or the get_success_url method.")
def get_not_modified_message(self) -> str:
"""Returns the message when the data was not modified."""
return self.not_modified_message
def get_success_message(self) -> str:
"""Returns the success message."""
return self.success_message
def get_current_object(self) -> Optional[Model]:
"""Finds and returns the current object, or None on a create form."""
if "pk" in self.request.resolver_match.kwargs:
pk = self.request.resolver_match.kwargs["pk"]
try:
return self._model.objects.get(pk=pk)
except self._model.DoesNotExist:
raise Http404
return None
class DeleteView(SuccessMessageMixin, CoreDeleteView): class DeleteView(SuccessMessageMixin, CoreDeleteView):
@ -79,73 +229,52 @@ class UserView(DetailView):
return self.request.resolver_match.kwargs["user"] return self.request.resolver_match.kwargs["user"]
@require_GET @method_decorator(login_required, name="dispatch")
@login_required class UserFormView(FormView):
def user_form(request: HttpRequest, user: User = None) -> HttpResponse: model = User
"""The view to edit an accounting transaction. form = UserForm
not_modified_message = gettext_noop("This user account was not changed.")
success_message = gettext_noop("This user account was saved successfully.")
Args: def make_form_from_post(self, post: Dict[str, str]) -> UserForm:
request: The request. """Creates and returns the form from the POST data."""
user: The account.
Returns:
The response.
"""
previous_post = stored_post.get_previous_post(request)
if previous_post is not None:
form = UserForm(previous_post)
elif user is not None:
form = UserForm({
"login_id": user.login_id,
"name": user.name,
"is_disabled": user.is_disabled,
})
else:
form = UserForm()
form.user = user
form.current_user = request.user
return render(request, "mia_core/user_form.html", {
"form": form,
})
def user_store(request: HttpRequest,
user: User = None) -> HttpResponseRedirect:
"""The view to store a user.
Args:
request: The request.
user: The user.
Returns:
The response.
"""
post = request.POST.dict()
strip_post(post)
form = UserForm(post) form = UserForm(post)
form.user = user form.user = self.get_current_object()
form.current_user = request.user form.current_user = self.request.user
if not form.is_valid(): return form
if user is None:
url = reverse("mia_core:users.create") def make_form_from_model(self, obj: User) -> forms.Form:
else: """Creates and returns the form from a data model."""
url = reverse("mia_core:users.edit", args=(user,)) form = UserForm({
return stored_post.error_redirect(request, url, post) "login_id": obj.login_id,
if user is None: "name": obj.name,
user = User() "is_disabled": obj.is_disabled,
user.login_id = form["login_id"].value() })
form.user = self.get_current_object()
form.current_user = self.request.user
return form
def fill_model_from_form(self, obj: User, form: UserForm) -> None:
"""Fills in the data model from the form."""
obj.login_id = form["login_id"].value()
if form["password"].value() is not None: if form["password"].value() is not None:
user.set_digest_password( obj.set_digest_password(
form["login_id"].value(), form["password"].value()) form["login_id"].value(), form["password"].value())
user.name = form["name"].value() obj.name = form["name"].value()
user.is_disabled = form["is_disabled"].value() obj.is_disabled = form["is_disabled"].value()
if not user.is_dirty(): obj.current_user = self.request.user
message = gettext_noop("This user account was not changed.")
else: def get_error_url(self) -> str:
user.save(current_user=request.user) """Returns the URL on error."""
message = gettext_noop("This user account was saved successfully.") user = self.get_current_object()
messages.success(request, message) return reverse("mia_core:users.create") if user is None\
return redirect("mia_core:users.detail", user) else reverse("mia_core:users.edit", args=(user,))
def get_current_object(self) -> Optional[Model]:
"""Returns the current object, or None on a create form."""
if "user" in self.request.resolver_match.kwargs:
return self.request.resolver_match.kwargs["user"]
return None
@require_POST @require_POST