Project

General

Profile

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

calebasse / calebasse / cbv.py @ 05a6bb20

1
from datetime import datetime, date
2
from dateutil.relativedelta import relativedelta
3

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

    
11
from calebasse.ressources.models import Service
12
from calebasse.middleware.request import get_request
13
from calebasse.utils import is_super_user, is_validator
14

    
15
HOME_SERVICE_COOKIE = 'home-service'
16

    
17
class ReturnToObjectMixin(object):
18
    def get_success_url(self):
19
        return '../#object-' + str(self.object.pk)
20

    
21

    
22
class ServiceFormMixin(object):
23
    def get_form_kwargs(self):
24
        kwargs = super(ServiceFormMixin, self).get_form_kwargs()
25
        kwargs['service'] = self.service
26
        return kwargs
27

    
28

    
29
class ServiceViewMixin(object):
30
    service = None
31
    date = None
32
    popup = False
33
    cookies_to_clear = [('agenda-tabs', ), ('active-agenda', ), ('last-ressource', )]
34

    
35
    def clear_cookies(self, response, path):
36
        for cookie in self.cookies_to_clear:
37
            cookie_name = cookie[0]
38
            try:
39
                # delete the cookie for provided path
40
                response.delete_cookie(cookie[0], path = cookie[1])
41
            except IndexError:
42
                # if not use the current page path
43
                response.delete_cookie(cookie[0],
44
                                       path = '/'.join(path.split('/')[:3])
45
                                       )
46

    
47
    def dispatch(self, request, **kwargs):
48
        self.popup = request.GET.get('popup')
49
        if 'service' in kwargs:
50
            self.service = get_object_or_404(Service, slug=kwargs['service'])
51
        if 'date' in kwargs:
52
            try:
53
                self.date = datetime.strptime(kwargs.get('date'),
54
                        '%Y-%m-%d').date()
55
            except (TypeError, ValueError):
56
                raise Http404
57
        result = super(ServiceViewMixin, self).dispatch(request, **kwargs)
58
        if self.service:
59
            result.set_cookie(HOME_SERVICE_COOKIE, self.service.slug,
60
                    max_age=3600*24*365, httponly=True)
61
        self.clear_cookies(result, request.path)
62
        return result
63

    
64
    def get_context_data(self, **kwargs):
65
        context = super(ServiceViewMixin, self).get_context_data(**kwargs)
66
        context['url_name'] = resolve(self.request.path).url_name
67
        context['popup'] = self.popup
68
        if self.service is not None:
69
            context['service'] = self.service.slug
70
            context['service_name'] = self.service.name
71

    
72
        if self.date is not None:
73
            context['date'] = self.date
74
            context['previous_day'] = self.date + relativedelta(days=-1)
75
            context['next_day'] = self.date + relativedelta(days=1)
76
            context['previous_month'] = self.date + relativedelta(months=-1)
77
            context['next_month'] = self.date + relativedelta(months=1)
78

    
79
        context['role'] = None
80
        user = get_request().user
81
        if is_super_user(user):
82
            context['role'] = ['super', 'validator']
83
        elif is_validator(user):
84
            context['role'] = ['validator']
85
        context['today'] = date.today()
86
        return context
87

    
88

    
89
class TemplateView(ServiceViewMixin, base.TemplateView):
90
    pass
91

    
92

    
93
class AppTemplateFirstMixin(object):
94
    def get_template_names(self):
95
        names = []
96
        model = getattr(self, 'model', None)
97
        if not model:
98
            model = self.queryset.model
99
        if model is not None:
100
           opts = model._meta
101
           names.append("%s/%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix))
102
           names.append("%s/%s.html" % (opts.app_label, self.template_name_suffix.strip('_')))
103
        if getattr(self, 'template_name', None):
104
            names.append(self.template_name)
105
        return names
106

    
107

    
108
class ModelNameMixin(object):
109
    def get_context_data(self, **kwargs):
110
        ctx = super(ModelNameMixin, self).get_context_data(**kwargs)
111
        ctx['model_verbose_name_plural'] = self.model._meta.verbose_name_plural
112
        ctx['model_verbose_name'] = self.model._meta.verbose_name
113
        return ctx
114

    
115

    
116
class ListView(AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
117
        list_cbv.ListView):
118
    def get_context_data(self, **kwargs):
119
        ctx = super(AppTemplateFirstMixin, self).get_context_data(**kwargs)
120
        ctx.update(super(ModelNameMixin, self).get_context_data(**kwargs))
121
        ctx.update(super(ServiceViewMixin, self).get_context_data(**kwargs))
122
        ctx.update(super(list_cbv.ListView, self).get_context_data(**kwargs))
123
        if self.request.GET.get('new_id'):
124
            ctx['new_id'] = int(self.request.GET.get('new_id'))
125
        return ctx
126

    
127
class M2MFormMixin(object):
128
    '''Alos save Many2Many relations for model forms if needed.'''
129
    def form_valid(self, form):
130
        res = super(M2MFormMixin, self).form_valid(form)
131
        if hasattr(form, 'save_m2m'):
132
            form.save_m2m()
133
        return res
134

    
135
class DetailView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
136
        detail.DetailView):
137
    pass
138

    
139
class CreateView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
140
        edit.CreateView):
141
    pass
142

    
143

    
144
class DeleteView(AppTemplateFirstMixin,
145
        ModelNameMixin, ServiceViewMixin, edit.DeleteView):
146
    pass
147

    
148

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

    
153

    
154
class FormView(ServiceViewMixin, edit.FormView):
155
    pass
156

    
157

    
158
class ContextMixin(object):
159
    """
160
    A default context mixin that passes the keyword arguments received by
161
    get_context_data as the template context.
162
    """
163

    
164
    def get_context_data(self, **kwargs):
165
        if 'view' not in kwargs:
166
            kwargs['view'] = self
167
        return kwargs
168

    
169

    
170
class MultiFormMixin(ContextMixin):
171
    """
172
    A mixin that provides a way to show and handle multiple forms in a request.
173
    """
174

    
175
    initial = {}
176
    initials = {}
177
    forms_classes = None
178
    success_url = None
179

    
180
    def get_prefixes(self):
181
        return self.forms_classes.keys()
182

    
183
    def get_initial(self, prefix):
184
        """
185
        Returns the initial data to use for forms on this view.
186
        """
187
        return self.initials.get(prefix, self.initial).copy()
188

    
189
    def get_form_class(self, prefix):
190
        """
191
        Returns the form class to use in this view
192
        """
193
        return self.forms_classes[prefix]
194

    
195
    def get_form(self, form_class, prefix):
196
        """
197
        Returns an instance of the form to be used in this view.
198
        """
199
        return form_class(**self.get_form_kwargs(prefix))
200

    
201
    def get_current_prefix(self):
202
        """
203
        Returns the current prefix by parsing first keys in POST
204
        """
205
        keys = self.request.POST.keys() or self.request.FILES.keys()
206
        for key in keys:
207
            if '-' in key:
208
                return key.split('-', 1)[0]
209
        return None
210

    
211
    def get_forms(self):
212
        """
213
        Returns the dictionnary of forms instances
214
        """
215
        form_instances = {}
216
        for prefix in self.get_prefixes():
217
            form_instances[prefix] = self.get_form(self.get_form_class(prefix), prefix)
218
        return form_instances
219

    
220
    def get_form_kwargs(self, prefix):
221
        """
222
        Returns the keyword arguments for instantiating the form.
223
        """
224
        kwargs = {'initial': self.get_initial(prefix),
225
                  'prefix': prefix }
226
        if self.request.method in ('POST', 'PUT') \
227
                and prefix == self.get_current_prefix():
228
            kwargs.update({
229
                'data': self.request.POST,
230
                'files': self.request.FILES,
231
            })
232
        return kwargs
233

    
234
    def get_success_url(self):
235
        """
236
        Returns the supplied success URL.
237
        """
238
        if self.success_url:
239
            url = self.success_url
240
        else:
241
            raise ImproperlyConfigured(
242
                "No URL to redirect to. Provide a success_url.")
243
        return url
244

    
245
    def form_valid(self, forms):
246
        """
247
        If the form is valid, redirect to the supplied URL.
248
        """
249
        return HttpResponseRedirect(self.get_success_url())
250

    
251
    def form_invalid(self, forms):
252
        """
253
        If the form is invalid, re-render the context data with the
254
        data-filled form and errors.
255
        """
256
        return self.render_to_response(self.get_context_data(forms=forms))
257

    
258

    
259
class MultiModelFormMixin(MultiFormMixin, detail.SingleObjectMixin):
260
    """
261
    A mixin that provides a way to show and handle multiple forms or modelforms
262
    in a request.
263
    """
264

    
265
    def get_form_kwargs(self, prefix):
266
        """
267
        Returns the keyword arguments for instantiating the form.
268
        """
269
        kwargs = super(MultiModelFormMixin, self).get_form_kwargs(prefix)
270
        if issubclass(self.get_form_class(prefix), forms.ModelForm):
271
            kwargs.update({'instance': self.object})
272
        return kwargs
273

    
274
    def get_success_url(self):
275
        """
276
        Returns the supplied URL.
277
        """
278
        if self.success_url:
279
            url = self.success_url % self.object.__dict__
280
        else:
281
            try:
282
                url = self.object.get_absolute_url()
283
            except AttributeError:
284
                raise ImproperlyConfigured(
285
                    "No URL to redirect to.  Either provide a url or define"
286
                    " a get_absolute_url method on the Model.")
287
        return url
288

    
289
    def form_valid(self, form):
290
        """
291
        If the form is valid, save the associated model.
292
        """
293
        form = form[self.get_current_prefix()]
294
        if hasattr(form, 'save'):
295
            if isinstance(form, forms.ModelForm):
296
                self.object = form.save()
297
            else:
298
                form.save()
299
        if hasattr(form, 'save_m2m'): # save many2many relations
300
            form.save_m2m()
301
        return super(MultiModelFormMixin, self).form_valid(form)
302

    
303
    def get_context_data(self, **kwargs):
304
        """
305
        If an object has been supplied, inject it into the context with the
306
        supplied context_object_name name.
307
        """
308
        context = {}
309
        if self.object:
310
            context['object'] = self.object
311
            context_object_name = self.get_context_object_name(self.object)
312
            if context_object_name:
313
                context[context_object_name] = self.object
314
        context.update(kwargs)
315
        return super(MultiModelFormMixin, self).get_context_data(**context)
316

    
317

    
318
class ProcessMultiFormView(base.View):
319
    """
320
    A mixin that renders a form on GET and processes it on POST.
321
    """
322
    def get(self, request, *args, **kwargs):
323
        """
324
        Handles GET requests and instantiates a blank version of the form.
325
        """
326
        forms = self.get_forms()
327
        return self.render_to_response(self.get_context_data(forms=forms))
328

    
329
    def post(self, request, *args, **kwargs):
330
        """
331
        Handles POST requests, instantiating a form instance with the passed
332
        POST variables and then checked for validity.
333
        """
334
        forms = self.get_forms()
335
        prefix = self.get_current_prefix()
336
        if prefix and forms[prefix].is_valid():
337
            return self.form_valid(forms)
338
        else:
339
            return self.form_invalid(forms)
340

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

    
346

    
347
class BaseMultiFormView(MultiFormMixin, ProcessMultiFormView):
348
    """
349
    A base view for displaying multiple forms
350
    """
351

    
352

    
353
class MultiFormView(base.TemplateResponseMixin, BaseMultiFormView):
354
    """
355
    A base view for displaying multiple forms, and rendering a template reponse.
356
    """
357

    
358

    
359
class BaseMultiUpdateView(MultiModelFormMixin, ProcessMultiFormView):
360
    """
361
    Base view for updating an existing object.
362

    
363
    Using this base class requires subclassing to provide a response mixin.
364
    """
365
    def get(self, request, *args, **kwargs):
366
        self.object = self.get_object()
367
        return super(BaseMultiUpdateView, self).get(request, *args, **kwargs)
368

    
369
    def post(self, request, *args, **kwargs):
370
        self.object = self.get_object()
371
        return super(BaseMultiUpdateView, self).post(request, *args, **kwargs)
372

    
373

    
374
class MultiUpdateView(AppTemplateFirstMixin,
375
        detail.SingleObjectTemplateResponseMixin, BaseMultiUpdateView):
376
    """
377
    View for updating an object,
378
    with a response rendered by template.
379
    """
380
    template_name_suffix = '_form'
(3-3/17)