File: advanced.py

package info (click to toggle)
python-django-extra-views 0.14.0-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 468 kB
  • sloc: python: 1,591; makefile: 142; sh: 6
file content (254 lines) | stat: -rw-r--r-- 8,192 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
from django.contrib import messages
from django.forms.formsets import all_valid
from django.views.generic.base import ContextMixin
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import FormView, ModelFormMixin

from extra_views.formsets import BaseInlineFormSetFactory


class InlineFormSetFactory(BaseInlineFormSetFactory):
    """
    Class used to create an `InlineFormSet` from `inlineformset_factory` as
    one of multiple `InlineFormSet`s within a single view.

    Subclasses `BaseInlineFormSetFactory` and passes in the necessary view arguments.
    """

    def __init__(self, parent_model, request, instance, view_kwargs=None, view=None):
        self.inline_model = self.model
        self.model = parent_model
        self.request = request
        self.object = instance
        self.kwargs = view_kwargs
        self.view = view

    def construct_formset(self):
        """
        Overrides construct_formset to attach the model class as
        an attribute of the returned formset instance.
        """
        formset = super().construct_formset()
        formset.model = self.inline_model
        return formset


class ModelFormWithInlinesMixin(ModelFormMixin):
    """
    A mixin that provides a way to show and handle a modelform and inline
    formsets in a request.

    The inlines should be subclasses of `InlineFormSetFactory`.
    """

    inlines = []

    def get_inlines(self):
        """
        Returns the inline formset classes
        """
        return self.inlines

    def forms_valid(self, form, inlines):
        """
        If the form and formsets are valid, save the associated models.
        """
        response = self.form_valid(form)
        for formset in inlines:
            formset.save()
        return response

    def forms_invalid(self, form, inlines):
        """
        If the form or formsets are invalid, re-render the context data with the
        data-filled form and formsets and errors.
        """
        return self.render_to_response(
            self.get_context_data(form=form, inlines=inlines)
        )

    def construct_inlines(self):
        """
        Returns the inline formset instances
        """
        inline_formsets = []
        for inline_class in self.get_inlines():
            inline_instance = inline_class(
                self.model, self.request, self.object, self.kwargs, self
            )
            inline_formset = inline_instance.construct_formset()
            inline_formsets.append(inline_formset)
        return inline_formsets


class ProcessFormWithInlinesView(FormView):
    """
    A mixin that renders a form and inline formsets on GET and processes it on POST.
    """

    def get(self, request, *args, **kwargs):
        """
        Handles GET requests and instantiates a blank version of the form and formsets.
        """
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        inlines = self.construct_inlines()
        return self.render_to_response(
            self.get_context_data(form=form, inlines=inlines, **kwargs)
        )

    def post(self, request, *args, **kwargs):
        """
        Handles POST requests, instantiating a form and formset instances with the
        passed POST variables and then checked for validity.
        """
        form_class = self.get_form_class()
        form = self.get_form(form_class)

        initial_object = self.object
        if form.is_valid():
            self.object = form.save(commit=False)
            form_validated = True
        else:
            form_validated = False

        inlines = self.construct_inlines()

        if all_valid(inlines) and form_validated:
            return self.forms_valid(form, inlines)
        self.object = initial_object
        return self.forms_invalid(form, inlines)

    # PUT is a valid HTTP verb for creating (with a known URL) or editing an
    # object, note that browsers only support POST for now.
    def put(self, *args, **kwargs):
        return self.post(*args, **kwargs)


class BaseCreateWithInlinesView(ModelFormWithInlinesMixin, ProcessFormWithInlinesView):
    """
    Base view for creating an new object instance with related model instances.

    Using this base class requires subclassing to provide a response mixin.
    """

    def get(self, request, *args, **kwargs):
        self.object = None
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super().post(request, *args, **kwargs)


class CreateWithInlinesView(
    SingleObjectTemplateResponseMixin, BaseCreateWithInlinesView
):
    """
    View for creating a new object instance with related model instances,
    with a response rendered by template.
    """

    template_name_suffix = "_form"


class BaseUpdateWithInlinesView(ModelFormWithInlinesMixin, ProcessFormWithInlinesView):
    """
    Base view for updating an existing object with related model instances.

    Using this base class requires subclassing to provide a response mixin.
    """

    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)


class UpdateWithInlinesView(
    SingleObjectTemplateResponseMixin, BaseUpdateWithInlinesView
):
    """
    View for updating an object with related model instances,
    with a response rendered by template.
    """

    template_name_suffix = "_form"


class NamedFormsetsMixin(ContextMixin):
    """
    A mixin for use with `CreateWithInlinesView` or `UpdateWithInlinesView` that lets
    you define the context variable for each inline.
    """

    inlines_names = []

    def get_inlines_names(self):
        """
        Returns a list of names of context variables for each inline in `inlines`.
        """
        return self.inlines_names

    def get_context_data(self, **kwargs):
        """
        If `inlines_names` has been defined, add each formset to the context under
        its corresponding entry in `inlines_names`
        """
        context = {}
        inlines_names = self.get_inlines_names()

        if inlines_names:
            # We have formset or inlines in context, but never both
            context.update(zip(inlines_names, kwargs.get("inlines", [])))
            if "formset" in kwargs:
                context[inlines_names[0]] = kwargs["formset"]
        context.update(kwargs)
        return super().get_context_data(**context)


class SuccessMessageMixin(object):
    """
    Adds success message on views with inlines if django.contrib.messages framework
    is used.
    In order to use just add mixin in to inheritance before main class, e.g.:
    class MyCreateWithInlinesView (SuccessMessageMixin, CreateWithInlinesView):
        success_message='Something was created!'
    """

    success_message = ""

    def forms_valid(self, form, inlines):
        response = super().forms_valid(form, inlines)
        success_message = self.get_success_message(form.cleaned_data, inlines)
        if success_message:
            messages.success(self.request, success_message)
        return response

    def get_success_message(self, cleaned_data, inlines):
        return self.success_message % cleaned_data


class FormSetSuccessMessageMixin(object):
    """
    Adds success message on FormSet views if django.contrib.messages framework
    is used. In order to use just add mixin in to inheritance before main
    class, e.g.:
    class MyFormSetView (FormSetSuccessMessageMixin, ModelFormSetView):
        success_message='Something was created!'
    """

    success_message = ""

    def formset_valid(self, formset):
        response = super().formset_valid(formset)
        success_message = self.get_success_message(formset)
        if success_message:
            messages.success(self.request, success_message)
        return response

    def get_success_message(self, formset):
        return self.success_message