Skip to content

Commit

Permalink
New major release 3.0.0 (#216)
Browse files Browse the repository at this point in the history
* Fix waring from BSModalDeleteView / DeleteMessageMixin with Django 4

* fix when form has field named method

* add new mixin for FormValidation, remove  SuccessMessageMixin dependecy

* Updated project to current Django LTS version

Updated deprecated "is_safe_url" function to "url_has_allowed_host_and_scheme".
Added automatically database population, if DB is empty.
Updated requirements to current version.
Updated settings.
Removed outdated version support.
Updated gitignore.

* Minor refactoring

* Updated Project

Added type hints.
Updated test cases.
Removed last remaining snippets for outdated Django versions.

* Removed unused constant

* Updated required version

* Minor Bugfix

Removed __slots__ from dataclass, to match Python 3.8 support.

* refactor save method in CreateUpdateAjaxMixin

* remove compatibility.py

* remove utils.py

* remove types

* add is_ajax method and remove imports

* remove unneeded class inheritence

* remove obsolete class name parameter

* revert examples to version in master branch

* remove static folder

* remove types from tests

* remove unneeded comments

* update get and set for form action and method attributes

* update bootstrap5.modal.forms.min.js

* update assert string to pass the test

* update DeleteMessageMixin comment

* cleanup .gitignore

---------

Co-authored-by: Christian Wiegand <christianwgd@users.noreply.github.com>
Co-authored-by: Mark Monaghan <markmono@gmail.com>
Co-authored-by: aDramaQueen <richard.saeuberlich@o2online.de>
  • Loading branch information
4 people committed May 1, 2023
1 parent ca1fa95 commit 8887d2b
Show file tree
Hide file tree
Showing 22 changed files with 193 additions and 245 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ database/db.sqlite3
geckodriver.log
__pycache__
*.pyc
.env/
.env/
84 changes: 0 additions & 84 deletions bootstrap_modal_forms/compatibility.py

This file was deleted.

20 changes: 5 additions & 15 deletions bootstrap_modal_forms/generic.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import django
from django.contrib.messages.views import SuccessMessageMixin
from django.views import generic
from .mixins import PassRequestMixin, DeleteMessageMixin, LoginAjaxMixin
from django.contrib.auth.views import LoginView

DJANGO_VERSION = django.get_version().split('.')
DJANGO_MAJOR_VERSION = DJANGO_VERSION[0]
DJANGO_MINOR_VERSION = DJANGO_VERSION[1]
from .mixins import PassRequestMixin, DeleteMessageMixin, LoginAjaxMixin, FormValidationMixin

# Import custom LoginView for Django versions < 1.11
if DJANGO_MAJOR_VERSION == '1' and '11' not in DJANGO_MINOR_VERSION:
from .compatibility import LoginView
else:
from django.contrib.auth.views import LoginView


class BSModalLoginView(LoginAjaxMixin, SuccessMessageMixin, LoginView):
class BSModalLoginView(LoginAjaxMixin, LoginView):
pass


class BSModalFormView(PassRequestMixin, generic.FormView):
pass


class BSModalCreateView(PassRequestMixin, SuccessMessageMixin, generic.CreateView):
class BSModalCreateView(PassRequestMixin, FormValidationMixin, generic.CreateView):
pass


class BSModalUpdateView(PassRequestMixin, SuccessMessageMixin, generic.UpdateView):
class BSModalUpdateView(PassRequestMixin, FormValidationMixin, generic.UpdateView):
pass


Expand Down
79 changes: 51 additions & 28 deletions bootstrap_modal_forms/mixins.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,96 @@
from django.contrib import messages
from django.contrib.auth import login as auth_login
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, HttpResponse

from .utils import is_ajax


class PassRequestMixin(object):
class PassRequestMixin:
"""
Mixin which puts the request into the form's kwargs.
Form Mixin which puts the request into the form's kwargs.
Note: Using this mixin requires you to pop the `request` kwarg
out of the dict in the super of your form's `__init__`.
"""

def get_form_kwargs(self):
kwargs = super(PassRequestMixin, self).get_form_kwargs()
kwargs.update({'request': self.request})
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs


class PopRequestMixin(object):
class PopRequestMixin:
"""
Mixin which pops request out of the kwargs and attaches it to the form's
Form Mixin which pops request out of the kwargs and attaches it to the form's
instance.
Note: This mixin must precede forms.ModelForm/forms.Form. The form is not
expecting these kwargs to be passed in, so they must be popped off before
anything else is done.
"""

def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
self.request = kwargs.pop('request', None)
super(PopRequestMixin, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)


class CreateUpdateAjaxMixin(object):
class CreateUpdateAjaxMixin:
"""
Mixin which passes or saves object based on request type.
ModelForm Mixin which passes or saves object based on request type.
"""

def save(self, commit=True):
if not is_ajax(self.request.META) or self.request.POST.get('asyncUpdate') == 'True':
instance = super(CreateUpdateAjaxMixin, self).save(commit=commit)
else:
instance = super(CreateUpdateAjaxMixin, self).save(commit=False)
return instance
isAjaxRequest = is_ajax(self.request.META)
asyncUpdate = self.request.POST.get('asyncUpdate') == 'True'

if not isAjaxRequest or asyncUpdate:
return super().save(commit=commit)
if isAjaxRequest:
return super().save(commit=False)


class DeleteMessageMixin(object):
class DeleteMessageMixin:
"""
Mixin which adds message to BSModalDeleteView and only calls the delete method if request
is not ajax request.
Generic View Mixin which adds message to BSModalDeleteView and only calls the post method if request
is not ajax request. In case request is ajax post method calls delete method, which redirects to success url.
"""
def delete(self, request, *args, **kwargs):

def post(self, request, *args, **kwargs):
if not is_ajax(request.META):
messages.success(request, self.success_message)
return super(DeleteMessageMixin, self).delete(request, *args, **kwargs)
return super().post(request, *args, **kwargs)
else:
self.object = self.get_object()
return HttpResponseRedirect(self.get_success_url())

class LoginAjaxMixin(object):

class LoginAjaxMixin:
"""
Mixin which authenticates user if request is not ajax request.
Generic View Mixin which authenticates user if request is not ajax request.
"""

def form_valid(self, form):
if not is_ajax(self.request.META):
auth_login(self.request, form.get_user())
messages.success(self.request, self.success_message)
return HttpResponseRedirect(self.get_success_url())
return HttpResponseRedirect(self.get_success_url())


class FormValidationMixin:
"""
Generic View Mixin which saves object and redirects to success_url if request is not ajax request. Otherwise response 204 No content is returned.
"""

def form_valid(self, form):
isAjaxRequest = is_ajax(self.request.META)
asyncUpdate = self.request.POST.get('asyncUpdate') == 'True'

if isAjaxRequest:
if asyncUpdate:
form.save()
return HttpResponse(status=204)

form.save()
messages.success(self.request, self.success_message)
return HttpResponseRedirect(self.success_url)


def is_ajax(meta):
return 'HTTP_X_REQUESTED_WITH' in meta and meta['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
15 changes: 7 additions & 8 deletions bootstrap_modal_forms/static/js/bootstrap5.modal.forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const modalFormCallback = function (settings) {

let form = modal.querySelector(settings.modalForm);
if (form) {
form.action = settings.formURL;
form.setAttribute("action", settings.formURL);
addEventHandlers(modal, form, settings)
}
});
Expand Down Expand Up @@ -57,9 +57,9 @@ const isFormValid = function (settings, callback) {

let btnSubmit = modal.querySelector('button[type="submit"]');
btnSubmit.disabled = true;
fetch(form.action, {
fetch(form.getAttribute("action"), {
headers: headers,
method: form.method,
method: form.getAttribute("method"),
body: new FormData(form),
}).then(res => {
return res.text();
Expand All @@ -73,7 +73,7 @@ const isFormValid = function (settings, callback) {
return;
}

form.action = settings.formURL;
form.setAttribute("action", settings.formURL);
addEventHandlers(modal, form, settings)
} else {
callback(settings);
Expand All @@ -97,8 +97,8 @@ const submitForm = function (settings) {
// Add asyncUpdate and check for it in save method of CreateUpdateAjaxMixin
formData.append("asyncUpdate", "True");

fetch(form.action, {
method: form.method,
fetch(form.getAttribute("action"), {
method: form.getAttribute("method"),
body: formData,
}).then(res => {
return res.text();
Expand Down Expand Up @@ -142,7 +142,7 @@ const submitForm = function (settings) {
return;
}

form.action = settings.formURL;
form.setAttribute("action", settings.formURL);
addEventHandlers(modal, form, settings)
});
}
Expand All @@ -156,7 +156,6 @@ const submitForm = function (settings) {
};

const validateAsyncSettings = function (settings) {
console.log(settings)
var missingSettings = [];

if (!settings.successMessage) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 0 additions & 8 deletions bootstrap_modal_forms/utils.py

This file was deleted.

3 changes: 0 additions & 3 deletions examples/admin.py

This file was deleted.

2 changes: 1 addition & 1 deletion examples/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


class ExamplesConfig(AppConfig):
name = 'examples'
name = 'examples'
2 changes: 1 addition & 1 deletion examples/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ class Meta:
class CustomAuthenticationForm(AuthenticationForm):
class Meta:
model = User
fields = ['username', 'password']
fields = ['username', 'password']

0 comments on commit 8887d2b

Please sign in to comment.