Project

General

Profile

Download (12.7 KB) Statistics
| Branch: | Tag: | Revision:

calebasse / calebasse / cbv.py @ 99a62b84

1
# -*- coding: utf-8 -*-
2

    
3
from datetime import datetime, date
4
from dateutil.relativedelta import relativedelta
5

    
6
from django import forms
7
from django.views.generic import list as list_cbv, edit, base, detail
8
from django.shortcuts import get_object_or_404
9
from django.http import Http404, HttpResponseRedirect
10
from django.core.urlresolvers import resolve
11
from django.core.exceptions import ImproperlyConfigured
12
from django.contrib import messages
13

    
14
from calebasse.ressources.models import Service
15
from calebasse.middleware.request import get_request
16
from calebasse.utils import is_super_user, is_validator
17

    
18
HOME_SERVICE_COOKIE = 'home-service'
19

    
20
class NotificationDisplayView(object):
21

    
22
    def form_valid(self, form):
23
        valid = super(NotificationDisplayView, self).form_valid(form)
24
        messages.info(self.request, u'Modification enregistrée avec succès')
25
        return valid
26

    
27
class ReturnToObjectMixin(object):
28
    def get_success_url(self):
29
        return '../#object-' + str(self.object.pk)
30

    
31

    
32
class ServiceFormMixin(object):
33
    def get_form_kwargs(self):
34
        kwargs = super(ServiceFormMixin, self).get_form_kwargs()
35
        kwargs['service'] = self.service
36
        return kwargs
37

    
38

    
39
class ServiceViewMixin(object):
40
    service = None
41
    date = None
42
    popup = False
43
    cookies_to_clear = []
44

    
45
    def clear_cookies(self, response, path):
46
        for cookie in self.cookies_to_clear:
47
            cookie_name = cookie[0]
48
            try:
49
                # delete the cookie for provided path
50
                response.delete_cookie(cookie[0], path = cookie[1])
51
            except IndexError:
52
                # if not use the current page path
53
                response.delete_cookie(cookie[0],
54
                                       path = '/'.join(path.split('/')[:3])
55
                                       )
56

    
57
    def dispatch(self, request, **kwargs):
58
        self.popup = request.GET.get('popup')
59
        if 'service' in kwargs:
60
            self.service = get_object_or_404(Service, slug=kwargs['service'])
61
        if 'date' in kwargs:
62
            try:
63
                self.date = datetime.strptime(kwargs.get('date'),
64
                        '%Y-%m-%d').date()
65
            except (TypeError, ValueError):
66
                raise Http404
67
        result = super(ServiceViewMixin, self).dispatch(request, **kwargs)
68
        if self.service:
69
            result.set_cookie(HOME_SERVICE_COOKIE, self.service.slug,
70
                    max_age=3600*24*365, httponly=True)
71
        self.clear_cookies(result, request.path)
72
        return result
73

    
74
    def get_context_data(self, **kwargs):
75
        context = super(ServiceViewMixin, self).get_context_data(**kwargs)
76
        context['url_name'] = resolve(self.request.path).url_name
77
        context['popup'] = self.popup
78
        if self.service is not None:
79
            context['service'] = self.service.slug
80
            context['service_name'] = self.service.name
81

    
82
        if self.date is not None:
83
            context['date'] = self.date
84
            context['previous_day'] = self.date + relativedelta(days=-1)
85
            context['next_day'] = self.date + relativedelta(days=1)
86
            context['previous_month'] = self.date + relativedelta(months=-1)
87
            context['next_month'] = self.date + relativedelta(months=1)
88

    
89
        context['role'] = None
90
        user = get_request().user
91
        if is_super_user(user):
92
            context['role'] = ['super', 'validator']
93
        elif is_validator(user):
94
            context['role'] = ['validator']
95
        context['today'] = date.today()
96
        return context
97

    
98

    
99
class TemplateView(ServiceViewMixin, base.TemplateView):
100
    pass
101

    
102

    
103
class AppTemplateFirstMixin(object):
104
    def get_template_names(self):
105
        names = []
106
        model = getattr(self, 'model', None)
107
        if not model:
108
            model = self.queryset.model
109
        if model is not None:
110
           opts = model._meta
111
           names.append("%s/%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix))
112
           names.append("%s/%s.html" % (opts.app_label, self.template_name_suffix.strip('_')))
113
        if getattr(self, 'template_name', None):
114
            names.append(self.template_name)
115
        return names
116

    
117

    
118
class ModelNameMixin(object):
119
    def get_context_data(self, **kwargs):
120
        ctx = super(ModelNameMixin, self).get_context_data(**kwargs)
121
        ctx['model_verbose_name_plural'] = self.model._meta.verbose_name_plural
122
        ctx['model_verbose_name'] = self.model._meta.verbose_name
123
        return ctx
124

    
125

    
126
class ListView(AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
127
        list_cbv.ListView):
128
    def get_context_data(self, **kwargs):
129
        ctx = super(AppTemplateFirstMixin, self).get_context_data(**kwargs)
130
        ctx.update(super(ModelNameMixin, self).get_context_data(**kwargs))
131
        ctx.update(super(ServiceViewMixin, self).get_context_data(**kwargs))
132
        ctx.update(super(list_cbv.ListView, self).get_context_data(**kwargs))
133
        if self.request.GET.get('new_id'):
134
            ctx['new_id'] = int(self.request.GET.get('new_id'))
135
        return ctx
136

    
137
class M2MFormMixin(object):
138
    '''Alos save Many2Many relations for model forms if needed.'''
139
    def form_valid(self, form):
140
        res = super(M2MFormMixin, self).form_valid(form)
141
        if hasattr(form, 'save_m2m'):
142
            form.save_m2m()
143
        return res
144

    
145
class DetailView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
146
        detail.DetailView):
147
    pass
148

    
149
class CreateView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
150
        edit.CreateView):
151
    pass
152

    
153

    
154
class DeleteView(AppTemplateFirstMixin,
155
        ModelNameMixin, ServiceViewMixin, edit.DeleteView):
156
    pass
157

    
158

    
159
class UpdateView(M2MFormMixin, AppTemplateFirstMixin,
160
        ModelNameMixin, ServiceViewMixin, edit.UpdateView):
161
    pass
162

    
163

    
164
class FormView(ServiceViewMixin, edit.FormView):
165
    pass
166

    
167

    
168
class ContextMixin(object):
169
    """
170
    A default context mixin that passes the keyword arguments received by
171
    get_context_data as the template context.
172
    """
173

    
174
    def get_context_data(self, **kwargs):
175
        if 'view' not in kwargs:
176
            kwargs['view'] = self
177
        return kwargs
178

    
179

    
180
class MultiFormMixin(ContextMixin):
181
    """
182
    A mixin that provides a way to show and handle multiple forms in a request.
183
    """
184

    
185
    initial = {}
186
    initials = {}
187
    forms_classes = None
188
    success_url = None
189

    
190
    def get_prefixes(self):
191
        return self.forms_classes.keys()
192

    
193
    def get_initial(self, prefix):
194
        """
195
        Returns the initial data to use for forms on this view.
196
        """
197
        return self.initials.get(prefix, self.initial).copy()
198

    
199
    def get_form_class(self, prefix):
200
        """
201
        Returns the form class to use in this view
202
        """
203
        return self.forms_classes[prefix]
204

    
205
    def get_form(self, form_class, prefix):
206
        """
207
        Returns an instance of the form to be used in this view.
208
        """
209
        return form_class(**self.get_form_kwargs(prefix))
210

    
211
    def get_current_prefix(self):
212
        """
213
        Returns the current prefix by parsing first keys in POST
214
        """
215
        keys = self.request.POST.keys() or self.request.FILES.keys()
216
        for key in keys:
217
            if '-' in key:
218
                return key.split('-', 1)[0]
219
        return None
220

    
221
    def get_forms(self):
222
        """
223
        Returns the dictionnary of forms instances
224
        """
225
        form_instances = {}
226
        for prefix in self.get_prefixes():
227
            form_instances[prefix] = self.get_form(self.get_form_class(prefix), prefix)
228
        return form_instances
229

    
230
    def get_form_kwargs(self, prefix):
231
        """
232
        Returns the keyword arguments for instantiating the form.
233
        """
234
        kwargs = {'initial': self.get_initial(prefix),
235
                  'prefix': prefix }
236
        if self.request.method in ('POST', 'PUT') \
237
                and prefix == self.get_current_prefix():
238
            kwargs.update({
239
                'data': self.request.POST,
240
                'files': self.request.FILES,
241
            })
242
        return kwargs
243

    
244
    def get_success_url(self):
245
        """
246
        Returns the supplied success URL.
247
        """
248
        if self.success_url:
249
            url = self.success_url
250
        else:
251
            raise ImproperlyConfigured(
252
                "No URL to redirect to. Provide a success_url.")
253
        return url
254

    
255
    def form_valid(self, forms):
256
        """
257
        If the form is valid, redirect to the supplied URL.
258
        """
259
        return HttpResponseRedirect(self.get_success_url())
260

    
261
    def form_invalid(self, forms):
262
        """
263
        If the form is invalid, re-render the context data with the
264
        data-filled form and errors.
265
        """
266
        return self.render_to_response(self.get_context_data(forms=forms))
267

    
268

    
269
class MultiModelFormMixin(MultiFormMixin, detail.SingleObjectMixin):
270
    """
271
    A mixin that provides a way to show and handle multiple forms or modelforms
272
    in a request.
273
    """
274

    
275
    def get_form_kwargs(self, prefix):
276
        """
277
        Returns the keyword arguments for instantiating the form.
278
        """
279
        kwargs = super(MultiModelFormMixin, self).get_form_kwargs(prefix)
280
        if issubclass(self.get_form_class(prefix), forms.ModelForm):
281
            kwargs.update({'instance': self.object})
282
        return kwargs
283

    
284
    def get_success_url(self):
285
        """
286
        Returns the supplied URL.
287
        """
288
        if self.success_url:
289
            url = self.success_url % self.object.__dict__
290
        else:
291
            try:
292
                url = self.object.get_absolute_url()
293
            except AttributeError:
294
                raise ImproperlyConfigured(
295
                    "No URL to redirect to.  Either provide a url or define"
296
                    " a get_absolute_url method on the Model.")
297
        return url
298

    
299
    def form_valid(self, form):
300
        """
301
        If the form is valid, save the associated model.
302
        """
303
        form = form[self.get_current_prefix()]
304
        if hasattr(form, 'save'):
305
            if isinstance(form, forms.ModelForm):
306
                self.object = form.save()
307
            else:
308
                form.save()
309
        if hasattr(form, 'save_m2m'): # save many2many relations
310
            form.save_m2m()
311
        return super(MultiModelFormMixin, self).form_valid(form)
312

    
313
    def get_context_data(self, **kwargs):
314
        """
315
        If an object has been supplied, inject it into the context with the
316
        supplied context_object_name name.
317
        """
318
        context = {}
319
        if self.object:
320
            context['object'] = self.object
321
            context_object_name = self.get_context_object_name(self.object)
322
            if context_object_name:
323
                context[context_object_name] = self.object
324
        context.update(kwargs)
325
        return super(MultiModelFormMixin, self).get_context_data(**context)
326

    
327

    
328
class ProcessMultiFormView(base.View):
329
    """
330
    A mixin that renders a form on GET and processes it on POST.
331
    """
332
    def get(self, request, *args, **kwargs):
333
        """
334
        Handles GET requests and instantiates a blank version of the form.
335
        """
336
        forms = self.get_forms()
337
        return self.render_to_response(self.get_context_data(forms=forms))
338

    
339
    def post(self, request, *args, **kwargs):
340
        """
341
        Handles POST requests, instantiating a form instance with the passed
342
        POST variables and then checked for validity.
343
        """
344
        forms = self.get_forms()
345
        prefix = self.get_current_prefix()
346
        if prefix and forms[prefix].is_valid():
347
            return self.form_valid(forms)
348
        else:
349
            return self.form_invalid(forms)
350

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

    
356

    
357
class BaseMultiFormView(MultiFormMixin, ProcessMultiFormView):
358
    """
359
    A base view for displaying multiple forms
360
    """
361

    
362

    
363
class MultiFormView(base.TemplateResponseMixin, BaseMultiFormView):
364
    """
365
    A base view for displaying multiple forms, and rendering a template reponse.
366
    """
367

    
368

    
369
class BaseMultiUpdateView(MultiModelFormMixin, ProcessMultiFormView):
370
    """
371
    Base view for updating an existing object.
372

    
373
    Using this base class requires subclassing to provide a response mixin.
374
    """
375
    def get(self, request, *args, **kwargs):
376
        self.object = self.get_object()
377
        return super(BaseMultiUpdateView, self).get(request, *args, **kwargs)
378

    
379
    def post(self, request, *args, **kwargs):
380
        self.object = self.get_object()
381
        return super(BaseMultiUpdateView, self).post(request, *args, **kwargs)
382

    
383

    
384
class MultiUpdateView(AppTemplateFirstMixin,
385
        detail.SingleObjectTemplateResponseMixin, BaseMultiUpdateView):
386
    """
387
    View for updating an object,
388
    with a response rendered by template.
389
    """
390
    template_name_suffix = '_form'
(3-3/17)