Project

General

Profile

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

calebasse / calebasse / cbv.py @ 3bb50103

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-worker-tabs', 'active-resource-agenda' ]
34

    
35
    def clear_cookies(self, response):
36
        for cookie in self.cookies_to_clear:
37
            response.delete_cookie(cookie)
38

    
39
    def dispatch(self, request, **kwargs):
40
        self.popup = request.GET.get('popup')
41
        if 'service' in kwargs:
42
            self.service = get_object_or_404(Service, slug=kwargs['service'])
43
        if 'date' in kwargs:
44
            try:
45
                self.date = datetime.strptime(kwargs.get('date'),
46
                        '%Y-%m-%d').date()
47
            except (TypeError, ValueError):
48
                raise Http404
49
        result = super(ServiceViewMixin, self).dispatch(request, **kwargs)
50
        if self.service:
51
            result.set_cookie(HOME_SERVICE_COOKIE, self.service.slug,
52
                    max_age=3600*24*365, httponly=True)
53
        self.clear_cookies(result)
54
        return result
55

    
56
    def get_context_data(self, **kwargs):
57
        context = super(ServiceViewMixin, self).get_context_data(**kwargs)
58
        context['url_name'] = resolve(self.request.path).url_name
59
        context['popup'] = self.popup
60
        if self.service is not None:
61
            context['service'] = self.service.slug
62
            context['service_name'] = self.service.name
63

    
64
        if self.date is not None:
65
            context['date'] = self.date
66
            context['previous_day'] = self.date + relativedelta(days=-1)
67
            context['next_day'] = self.date + relativedelta(days=1)
68
            context['previous_month'] = self.date + relativedelta(months=-1)
69
            context['next_month'] = self.date + relativedelta(months=1)
70

    
71
        context['role'] = None
72
        user = get_request().user
73
        if is_super_user(user):
74
            context['role'] = ['super', 'validator']
75
        elif is_validator(user):
76
            context['role'] = ['validator']
77
        context['today'] = date.today()
78
        return context
79

    
80

    
81
class TemplateView(ServiceViewMixin, base.TemplateView):
82
    pass
83

    
84

    
85
class AppTemplateFirstMixin(object):
86
    def get_template_names(self):
87
        names = []
88
        model = getattr(self, 'model', None)
89
        if not model:
90
            model = self.queryset.model
91
        if model is not None:
92
           opts = model._meta
93
           names.append("%s/%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix))
94
           names.append("%s/%s.html" % (opts.app_label, self.template_name_suffix.strip('_')))
95
        if getattr(self, 'template_name', None):
96
            names.append(self.template_name)
97
        return names
98

    
99

    
100
class ModelNameMixin(object):
101
    def get_context_data(self, **kwargs):
102
        ctx = super(ModelNameMixin, self).get_context_data(**kwargs)
103
        ctx['model_verbose_name_plural'] = self.model._meta.verbose_name_plural
104
        ctx['model_verbose_name'] = self.model._meta.verbose_name
105
        return ctx
106

    
107

    
108
class ListView(AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
109
        list_cbv.ListView):
110
    def get_context_data(self, **kwargs):
111
        ctx = super(AppTemplateFirstMixin, self).get_context_data(**kwargs)
112
        ctx.update(super(ModelNameMixin, self).get_context_data(**kwargs))
113
        ctx.update(super(ServiceViewMixin, self).get_context_data(**kwargs))
114
        ctx.update(super(list_cbv.ListView, self).get_context_data(**kwargs))
115
        if self.request.GET.get('new_id'):
116
            ctx['new_id'] = int(self.request.GET.get('new_id'))
117
        return ctx
118

    
119
class M2MFormMixin(object):
120
    '''Alos save Many2Many relations for model forms if needed.'''
121
    def form_valid(self, form):
122
        res = super(M2MFormMixin, self).form_valid(form)
123
        if hasattr(form, 'save_m2m'):
124
            form.save_m2m()
125
        return res
126

    
127
class DetailView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
128
        detail.DetailView):
129
    pass
130

    
131
class CreateView(M2MFormMixin, AppTemplateFirstMixin, ModelNameMixin, ServiceViewMixin,
132
        edit.CreateView):
133
    pass
134

    
135

    
136
class DeleteView(AppTemplateFirstMixin,
137
        ModelNameMixin, ServiceViewMixin, edit.DeleteView):
138
    pass
139

    
140

    
141
class UpdateView(M2MFormMixin, AppTemplateFirstMixin,
142
        ModelNameMixin, ServiceViewMixin, edit.UpdateView):
143
    pass
144

    
145

    
146
class FormView(ServiceViewMixin, edit.FormView):
147
    pass
148

    
149

    
150
class ContextMixin(object):
151
    """
152
    A default context mixin that passes the keyword arguments received by
153
    get_context_data as the template context.
154
    """
155

    
156
    def get_context_data(self, **kwargs):
157
        if 'view' not in kwargs:
158
            kwargs['view'] = self
159
        return kwargs
160

    
161

    
162
class MultiFormMixin(ContextMixin):
163
    """
164
    A mixin that provides a way to show and handle multiple forms in a request.
165
    """
166

    
167
    initial = {}
168
    initials = {}
169
    forms_classes = None
170
    success_url = None
171

    
172
    def get_prefixes(self):
173
        return self.forms_classes.keys()
174

    
175
    def get_initial(self, prefix):
176
        """
177
        Returns the initial data to use for forms on this view.
178
        """
179
        return self.initials.get(prefix, self.initial).copy()
180

    
181
    def get_form_class(self, prefix):
182
        """
183
        Returns the form class to use in this view
184
        """
185
        return self.forms_classes[prefix]
186

    
187
    def get_form(self, form_class, prefix):
188
        """
189
        Returns an instance of the form to be used in this view.
190
        """
191
        return form_class(**self.get_form_kwargs(prefix))
192

    
193
    def get_current_prefix(self):
194
        """
195
        Returns the current prefix by parsing first keys in POST
196
        """
197
        keys = self.request.POST.keys() or self.request.FILES.keys()
198
        for key in keys:
199
            if '-' in key:
200
                return key.split('-', 1)[0]
201
        return None
202

    
203
    def get_forms(self):
204
        """
205
        Returns the dictionnary of forms instances
206
        """
207
        form_instances = {}
208
        for prefix in self.get_prefixes():
209
            form_instances[prefix] = self.get_form(self.get_form_class(prefix), prefix)
210
        return form_instances
211

    
212
    def get_form_kwargs(self, prefix):
213
        """
214
        Returns the keyword arguments for instantiating the form.
215
        """
216
        kwargs = {'initial': self.get_initial(prefix),
217
                  'prefix': prefix }
218
        if self.request.method in ('POST', 'PUT') \
219
                and prefix == self.get_current_prefix():
220
            kwargs.update({
221
                'data': self.request.POST,
222
                'files': self.request.FILES,
223
            })
224
        return kwargs
225

    
226
    def get_success_url(self):
227
        """
228
        Returns the supplied success URL.
229
        """
230
        if self.success_url:
231
            url = self.success_url
232
        else:
233
            raise ImproperlyConfigured(
234
                "No URL to redirect to. Provide a success_url.")
235
        return url
236

    
237
    def form_valid(self, forms):
238
        """
239
        If the form is valid, redirect to the supplied URL.
240
        """
241
        return HttpResponseRedirect(self.get_success_url())
242

    
243
    def form_invalid(self, forms):
244
        """
245
        If the form is invalid, re-render the context data with the
246
        data-filled form and errors.
247
        """
248
        return self.render_to_response(self.get_context_data(forms=forms))
249

    
250

    
251
class MultiModelFormMixin(MultiFormMixin, detail.SingleObjectMixin):
252
    """
253
    A mixin that provides a way to show and handle multiple forms or modelforms
254
    in a request.
255
    """
256

    
257
    def get_form_kwargs(self, prefix):
258
        """
259
        Returns the keyword arguments for instantiating the form.
260
        """
261
        kwargs = super(MultiModelFormMixin, self).get_form_kwargs(prefix)
262
        if issubclass(self.get_form_class(prefix), forms.ModelForm):
263
            kwargs.update({'instance': self.object})
264
        return kwargs
265

    
266
    def get_success_url(self):
267
        """
268
        Returns the supplied URL.
269
        """
270
        if self.success_url:
271
            url = self.success_url % self.object.__dict__
272
        else:
273
            try:
274
                url = self.object.get_absolute_url()
275
            except AttributeError:
276
                raise ImproperlyConfigured(
277
                    "No URL to redirect to.  Either provide a url or define"
278
                    " a get_absolute_url method on the Model.")
279
        return url
280

    
281
    def form_valid(self, form):
282
        """
283
        If the form is valid, save the associated model.
284
        """
285
        form = form[self.get_current_prefix()]
286
        if hasattr(form, 'save'):
287
            if isinstance(form, forms.ModelForm):
288
                self.object = form.save()
289
            else:
290
                form.save()
291
        if hasattr(form, 'save_m2m'): # save many2many relations
292
            form.save_m2m()
293
        return super(MultiModelFormMixin, self).form_valid(form)
294

    
295
    def get_context_data(self, **kwargs):
296
        """
297
        If an object has been supplied, inject it into the context with the
298
        supplied context_object_name name.
299
        """
300
        context = {}
301
        if self.object:
302
            context['object'] = self.object
303
            context_object_name = self.get_context_object_name(self.object)
304
            if context_object_name:
305
                context[context_object_name] = self.object
306
        context.update(kwargs)
307
        return super(MultiModelFormMixin, self).get_context_data(**context)
308

    
309

    
310
class ProcessMultiFormView(base.View):
311
    """
312
    A mixin that renders a form on GET and processes it on POST.
313
    """
314
    def get(self, request, *args, **kwargs):
315
        """
316
        Handles GET requests and instantiates a blank version of the form.
317
        """
318
        forms = self.get_forms()
319
        return self.render_to_response(self.get_context_data(forms=forms))
320

    
321
    def post(self, request, *args, **kwargs):
322
        """
323
        Handles POST requests, instantiating a form instance with the passed
324
        POST variables and then checked for validity.
325
        """
326
        forms = self.get_forms()
327
        prefix = self.get_current_prefix()
328
        if forms[prefix].is_valid():
329
            return self.form_valid(forms)
330
        else:
331
            return self.form_invalid(forms)
332

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

    
338

    
339
class BaseMultiFormView(MultiFormMixin, ProcessMultiFormView):
340
    """
341
    A base view for displaying multiple forms
342
    """
343

    
344

    
345
class MultiFormView(base.TemplateResponseMixin, BaseMultiFormView):
346
    """
347
    A base view for displaying multiple forms, and rendering a template reponse.
348
    """
349

    
350

    
351
class BaseMultiUpdateView(MultiModelFormMixin, ProcessMultiFormView):
352
    """
353
    Base view for updating an existing object.
354

    
355
    Using this base class requires subclassing to provide a response mixin.
356
    """
357
    def get(self, request, *args, **kwargs):
358
        self.object = self.get_object()
359
        return super(BaseMultiUpdateView, self).get(request, *args, **kwargs)
360

    
361
    def post(self, request, *args, **kwargs):
362
        self.object = self.get_object()
363
        return super(BaseMultiUpdateView, self).post(request, *args, **kwargs)
364

    
365

    
366
class MultiUpdateView(AppTemplateFirstMixin,
367
        detail.SingleObjectTemplateResponseMixin, BaseMultiUpdateView):
368
    """
369
    View for updating an object,
370
    with a response rendered by template.
371
    """
372
    template_name_suffix = '_form'
(3-3/14)