Django: correct use of class-based View inheritance? - python

I would like almost every View in my Django project to compute, for instance, time_on_mars. This variable needs to be put into every template context, so every template can draw time_on_mars. I'd also like every View to be able to use that variable if it wishes, maybe to compute is_it_nighttime_on_mars or whatever. I'd like to do this using inheritance, or some other mechanism, so when I provision a whole lot more than mars time, I don't repeat myself.
Is this an appropriate use of class-based Views? I guess I'd create a base class view, and in its get and post methods, calculate the current time on mars, then call child class methods 'doGet' or 'doPost', intercept the result, put the mars time into the result, and continue. Seems tedious.
Decorators? Is there a way I can put time_on_mars into the closure context for the view?
What's the pythonic (djangonic?) way to do this?
EDIT: I like the idea of context processors, but I've changed my mind - instead of every view, I'd like most views to get this. Especially if it's expensive to compute the time on mars...
EDIT 2: Problem description not adequate! To clarify:
I have a bunch of views that do this:
def some_view(request):
w,x,y,z = provision_wxyz()
... some biz logic, maybe compute a,b,c using w,x,y,z ...
return render(request, 'some_template.html', { 'a':a, 'b':b, 'c':c, 'w':w, 'x':x, 'y':y, 'z':z })
... and I'd like to abstract out the first step (w,x,y,z=), and the adding of w,x,y,z to the template context. Context processors don't work, because inside the some_view business logic, I want access to w,x,y,z. I don't like doing the mixing strategy for CLA on the TemplateView, because again, it doesn't give me access to w,x,y and z inside some_view (or whatever the equivalent of some_view is), and I want to, well, do a bunch of business logic somewhere, and TemplateView doesn't seem to give me that?

You can definitely use CBV's for this. It is a great use for Mixins which take advantage of pythons multiple inheritance. Here is a quick and dirty example I just wrote out.
class MarsMixin(object):
time_on_mars = 5
def get_time_on_mars(self):
"""
Does what it takes to return a time on mars be it calculation
or returning a property set on the object. Should return a property
from the object if it is a constant. Should calcualte in the method
if it is going to be dynanic
"""
return self.time_on_mars
def get_context_data(self, **kwargs):
context = super(MarsMixin, self).get_context_data(**kwargs)
context['time_on_mars'] = self.get_time_on_mars()
return context
class HomeView(MarsMixin, TemplateView):
template_name = 'home/index.html'
Biggest notes are the mixin inherits from object. The other is you inherit from mixin in the HomeView and it is listed before the TemplateView.

I think you can still use context processors, the tip that can help is to prefix the urls where you want to do your calculation, for example they will start by '/mars/...'. You can also use another kind of prefix.
Like this you can check on the context processor method:
def go_to_mars(request):
if request.path.startswith('/mars/'):
return {'time_on_mars': calculate_time_on_mars()}
return {}

Related

Add extra parameter to inlineformset_factory

After a few hours of searching I must admit that I am defeated.
I have read the Django docs but I really can't find a solution to my problem.
Consider the following line of code:
EmploymentFormSet = inlineformset_factory(Profile, Employment, form=EmploymentForm, extra=3)
This code lives in a classbased view which inherits from UpdateView and furthermore in the method get_context_data(self, *args, **kwargs):
This is pretty straight forward as the inlineformset_factory creates an EmploymentFormSet.
Now consider this
queryset = Employment.objects.filter(profile__pk=self.kwargs['pk']).values()
context['emp_formset'] = EmploymentFormSet(prefix='emp_form', initial=queryset, auto_id=True)
I thought by supplying initial=queryset, which only applies to unbound instances IIRC, it would populate my formset with as many as the queryset would contain.
So the queryset will in my case return 4 Employments but when using the extra parameter, the formset I'm constructing are only filled with as many as this parameter defines, in my example only 3 since I defined only 3 extras. Incrementing the extra will populate the forms incrementally.
I've tried subclassing the BaseInlineFormSet but I haven't really broken through the wall.
My question is how would I go about to populate the formset with as many forms as the queryset contains, I'm not really out for an exact solution but more of a pointer in the right the direction! :)
Thanks!
I solved it by constructing this method
def set_extra_forms(extra_forms, **kwargs):
EmploymentFormSet = inlineformset_factory(Profile, Employment, form=EmploymentForm, extra=extra_forms)
return EmploymentFormSet(**kwargs)
Now I do believe this is the way to go but I have to refactor the code in order to make it more dynamic right now its connected to one class and one class only but it works like a charm.
Define the formset inside a view
def employment_view(request, pk=None):
#Define extra forms variable
extra_forms = Employment.objects.filter(profile__pk=self.kwargs['pk']).count()
#Define formset inside the view function
EmploymentFormSet = inlineformset_factory(Profile, Employment, form=EmploymentForm, extra=extra_forms)
#Use this in context
context['emp_formset'] = EmploymentFormSet()

Changing the display order of forms in a formset

I am displaying a modelformset and I would like the forms to be ordered by the contents of one of its fields. So I want to use the equivalent of SomeModel.objects.filter(whatever).order_by('somefield') for a (model)formset in a template.
How can I do this?
Note that can_order does not do what I want (it must be automatic, not user specified). I have also tried other things, like the dictsort filter, but that produces unpredictable output (i.e. not ordered by the specified field).
I even tried {% regroup formset by somefield as sorted_formset %}, but the resulting sorted_formset cannot be used (iterated) as a normal formset.
To complete the answers. There are two ways how you can control the order of forms in the formset: formsets respect order of given queryset (this is shown in other replies). Another way (if you want to have the order of the forms fully under control) is to define custom formset class and override __iter__ and __getitem__ methods:
from django.forms import BaseModelFormSet
class MyModelBaseFormset(BaseModelFormSet):
def __iter__(self):
"""Yields the forms in the order they should be rendered"""
return ...
def __getitem__(self, index):
"""Returns the form at the given index, based on the rendering order"""
return ...
MyModelFormset = modelformset_factory(model=MyModel, formset=MyModelBaseFormset, queryset=...)
This approach is described in the Django documentation:
Iterating over the formset will render the forms in the order they
were created. You can change this order by providing an alternate
implementation for the __iter__() method.
Formsets can also be indexed into, which returns the corresponding
form. If you override __iter__, you will need to also override
__getitem__ to have matching behavior.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#django.forms.formsets.BaseFormSet
The example of implementing these methods is for example in this SO thread: modelformset __iter__ overloading problem.
if you didn't defined Formset, this is the "inline code" version:
FS=inlineformset_factory(ParentClass,ChildClass)
formset=FS(instance=parentobject,
queryset=parentobject.childobject_set.order_by("-time_begin")
)
Thanks to #john-peters answer above, for pointing me in the right direction. But here is a better way:
MyFormset(inlineformset_factory(...)):
def get_queryset(self):
return super(MyFormset, self).get_queryset().order_by('myfieldname')
This way you do not have to copy or mess with django's code and potentially cause breakage down the road.. just take the queryset that django gives you and override the ordering. I have used this in my own code, it works.
EDIT. After working on this a bit I realize that although it appears to work fine, it somehow messes up the logic in BaseInlineFormset.get_queryset(), resulting in duplicate database queries. However, in the hopes that someone will comment on this and correct it, I will leave it here. Meanwhile, I have another solution which DOES WORK and does not result in redundant queries.. as follows:
MyFormset(inlineformset_factory(...)):
def __init__(self, *args, **kwargs):
super(MyFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('myfieldname')
This modifies the queryset at a safe time, before anything is done with it. In my code I am also doing .select_related() here, which greatly speeds up my large modelformsets!
Thanks to rantanplan's comment I found a solution. I could not use the method as described in the link above because I don't know what the queryset is going to be (this is a complex form with nested formsets).
Anyway, I found a solution by overriding the get_queryset method from Django's BaseInlineFormSet Class.
I copied it below, including my mod, in case googlers find it helpful.
def get_queryset(self):
'''
Copied this method from Django code and modified the ordering statement
'''
if not hasattr(self, '_queryset'):
if self.queryset is not None:
qs = self.queryset
else:
qs = self.model._default_manager.get_query_set()
# If the queryset isn't already ordered we need to add an
# artificial ordering here to make sure that all formsets
# constructed from this queryset have the same form order.
if not qs.ordered:
# MY MOD IS HERE:
# qs = qs.order_by(self.model._meta.pk.name)
qs = qs.order_by('order_index')
#/MOD
# Removed queryset limiting here. As per discussion re: #13023
# on django-dev, max_num should not prevent existing
# related objects/inlines from being displayed.
self._queryset = qs
return self._queryset
A more simpler way but it might not apply in every situation but if you can afford it just define ordering in models Meta class:
class ExampleModel(models.Model):
...
class Meta:
ordering = ("name", )
Formset will respect the queryset's ordering which will respect the Meta class variable's value.

Avoid passing RequestContext in every render in django

I am developing a site right now that, once you are logged in, a search bar will always be present on the top of the page. I am wondering what the best way design this paradigm in Django. Currently, I have a separate file called forms.py that sits at settings.py level in my folder hierarchy. In almost every view, I have to add:
from forms.py import SearchForm
and then in every single render call, I have to pass:
form = SearchForm()
return render('somepage.html',{"search_form" : form},c=RequestContext())
I have looked around for a better way of doing this, but I am having trouble finding anything useful. I have a feeling that the current design I am using is not ideal, since I am required to import/pass as parameter in almost every view.
The form is defined in a base.html, so I am using template inheritance, but I still need to pass the form object to every render as far as I can tell.
Use a context processor
Add your search form to the context of all views using RequestContext, which the new render you're using does automatically.
def FormContextProcessor(request):
if request.user.is_authenticated():
return {'form': SearchForm() }
return {}
You said it's used in nearly all views, and this is hardly an expensive operation instantiating a form, so I'd use this solution.
with django < 1.3 you can have a decorator, which can take care of rendering a html:
def search_render(function):
# return a decorated function which will take template from the args
# take output of the inner function (this should be a dictionary e.g. data = ..
# instantiate SearchForm
# add SearchForm instance to the data dictionary
# and return render(template, data, RequestContext(request))
#search_render(tamplate='somepage.html')
def my_other_view(request):
return {'data':'value'}
With django >= 1.3 you can use a class based views, with similiar approach.

Setting object owner with generic create_object view in django

Is it possible to use create_object view to create a new object and automatically assign request.user as foreign key?
P.E:
class Post(models.Model):
text = models.TextField()
author = models.ForeignKey(User)
What I want is to use create_object and fill author with request.user.
In many ways, all the solutions to this will be more trouble than they are worth. This one qualifies as a hack. It is possible for a django update to leave you high and dry if they change the way create_update is implemented. For simplicity sake, I'll assume that you are trying to set a default user, not silently force the user to be the logged in user.
Write a context processor:
from django.views.generic.create_update import get_model_and_form_class
def form_user_default(request):
if request.method == 'GET':
model, custom_form = get_model_and_form_class(Post,None)
custom_form.author = request.user
return {'form':custom_form}
else: return {}
What this will do is override the form object that create_update passes to the template. What it's technically doing is re-creating the form after the default view has done it.
Then in your url conf:
url(r'pattern_to_match', 'django.views.generic.create_update.create_object', kwargs={'context_processors':form_user_default})
Again, I had to delve into the source code to figure out how to do this. It might really be best to try writing your own view (but incorporate as many Django custom objects as possible). There's no "simple default" way to do this, because in the django paradigm forms are more closely tied to the model layer than to views, and only views have knowledge of the request object.
You may want to consider a closure.
from django.forms import ModelForm
from django.views.generic.create_update import create_object, update_object
def make_foo_form(request):
class FooForm(ModelForm):
class Meta:
model = Foo
fields = ['foo', 'bar']
def save(self, commit=True):
f = super(FooForm, self).save(commit=False)
if not f.pk: f.user = request.user
if commit: f.save()
return f
return FooForm
def create_foo(request):
FooForm = make_foo_form(request)
return create_object(form_class=FooForm)
There is some inefficiency here, since you need to create the ModelForm object on each request, but it does allow you to inject functionality into the generic view.
You need to decide whether the added complexity for the form creation is worth maintaining simplicity on the view side.
A benefit here, though, is that this also works with the update case with practically no extra effort:
def update_foo(request, object_id):
FooForm = make_foo_form(request)
return update_object(form_class=FooForm, object_id=object_id)
Obviously, you can use this approach for more complex cases as well.
If a user is authenticated, their user object is the request.user object.
I'm not familiar with create_object... I'm still a beginner to django and have only just started my first real project with it.
Note that you should check to make sure a user is logged in before using this. This can be done with request.user.is_authenticated().
There is no good way to hook into the saving of an object when using the current Django generic views. Once they are rewritten as classes, you'll be able to subclass the view and hook in at the proper place without having to rewrite the whole view.
I already use my own class-based generic views for this reason.
I would suggest to make a wrapper for the create_object, as this author suggest
http://www.b-list.org/weblog/2006/nov/16/django-tips-get-most-out-generic-views/
in the view you'll have access to the user info.
Afterwards, you will need to use the extra_context to pass the user to the template. Finally at the template you can add a hidden field with the user info. I haven't tried it, but I have been thinking of it for quite some time. Hope this solution suits you!
;) cheers!

Keeping filters in Django Admin

What I would like to achive is:
I go to admin site, apply some filters to the list of objects
I click and object edit, edit, edit, hit 'Save'
Site takes me to the list of objects... unfiltered. I'd like to have the filter from step 1 remembered and applied.
Is there an easy way to do it?
Unfortunately there's no easy way to do this. The filtering does not seem to be saved in any session variable.
Clicking back twice is the normal method, but it can be unweildy and annoying if you've just changed an object so that it should no longer be shown using your filter.
If it's just a one-off, click back twice or go through the filtering again, it's the easiest way.
If you're going to be filtering more often, or you just want to learn about hacking the admin (which is pretty open and easy), you'll want to write a FilterSpec.
Have a look here and here for examples of people writing their own.
A really, really terrible way to do this would be to edit the admin interface so that after you click "Save", you are redirected to you filtered URL. I wouldn't recommend this at all, but it's an option.
Another fairly simple way to do this would be to write a generic view to show your filtered objects, then use Django forms to edit the items from there. I'd have a look at this, you'll be stunned just how little code you have to write to get a simple view/edit page going.
Click 2 times "Back"?
There's a simple hack to do this, but it's not a general solution and requires modifying every ModelAdmin which you want to support this. Maybe there is a general way to do this, but I've not spent the time to solve it on a general level.
The first step is to write a custom FilterSpec for the filter (see Harley's post for links that will help) which saves the chosen filter value in the session (and deletes it when no longer wanted).
# in cust_admin/filterspecs.py
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec
class MyFilterSpec(ChoicesFilterSpec):
def __init__(self, f, request, params, model, model_admin):
super(MyFilterSpec, self).__init__(f, request, params, model,
model_admin)
if self.lookup_val is not None:
request.session[self.lookup_kwarg] = self.lookup_val
elif self.lookup_kwarg in request.session:
del(request.session[self.lookup_kwarg])
# Register the filter with a test function which will apply it to any field
# with a my_filter attribute equal to True
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'my_filter', False),
MyFilterSpec))
You must import the module this is in somewhere, for example your urls.py:
# in urls.py
from cust_admin import filterspecs
Set a property on the field you want to apply the filter to:
# in models.py
class MyModel(models.Model):
my_field = Models.IntegerField(choices=MY_CHOICES)
my_field.my_filter = True
In a custom ModelAdmin class, override the change_view method, so that after the user clicks save, they are returned to the list view with their filter field value added to the URL.
class MyModelAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(MyModelAdmin, self).change_view(request, object_id,
extra_context)
if '_save' in request.POST:
if 'my_field__exact' in request.session:
result['Location'] = '/admin/myapp/mymodel/?my_field__exact=%s' \
% request.session['my_field__exact']
return result
Another way to do this is to embed the filter in the queryset.
You can dynamically create a proxy model with a manager that filters the way you want, then call admin.site.register() to create a new model admin. All the links would then be relative to this view.
In my opinion its better to override methods from ModelAdmin changelist_view and change_view:
Like so:
class FakturaAdmin(admin.ModelAdmin):
[...]
def changelist_view(self, request, extra_context=None):
result = super(FakturaAdmin, self).changelist_view(request, extra_context=None)
request.session['qdict'] = request.GET
return result
def change_view(self, request, object_id, extra_context=None):
result = super(FakturaAdmin, self).change_view(request, object_id, extra_context)
try:
result['location'] = result['location']+"?"+request.session['qdict'].urlencode()
except:
pass
return result
As you wish, after save object you go back to list of objects with active filters.
There is a change request at the Django project asking for exactly this functionality.
All it's waiting for to be checked in is some tests and documentation. You could write those, and help the whole project, or you could just take the proposed patch (near the bottom of the page) and try it out.
https://code.djangoproject.com/ticket/6903
This feature has been added to Django as part of the 1.6 release and is enabled now by default. It is described in the release notes:
ModelAdmin now preserves filters on the list view after creating,
editing or deleting an object. It’s possible to restore the previous
behavior of clearing filters by setting the preserve_filters attribute
to False.

Categories

Resources