Mixin for Django generic views, how can object have a superclass? - python

So the background detail, Post is a model, and I am basically trying to create a blog, the same blog as the one shown in this video.
Here is the code:
from django.views.generic import ListView, DetailView
from models import Post
class PublishedPostsMixin(object):
def get_queryset(self):
queryset = super(PublishedPostsMixin, self).get_queryset()
return queryset.filter(published=True)
class PostListView(PublishedPostsMixin, ListView):
# PostListView takes default template name as `post_list.html`,
# as list was the name it was assigned.
model = Post
template_name = 'blog/post_list.html'
class PostDetailView(PublishedPostsMixin, DetailView):
model = Post
template_name = 'blog/post_detail.html'
So, if you can see, PublishedPostsMixin is inheriting from object, so how is it that the super() is working. If you can understand what is going on, could you please explain step by step, I'm a little confused.

The trick is in what super does. It's a dynamic call: it refers to the next class up in the MRO (method resolution order). Because (as Adrián says in the comments) the mixin is only supposed to be used in conjunction with other classes, there will always be something in between PublishedPostsMixin and object in the MRO.
For more details on super, you should read Raymond Hettinger's article Super considered super (note that it's written with Python 3 syntax in mind, but the principles are the same).

Related

Django: Common/Reusable ModelAdmin Class

I have some functions like:
has_delete_permission,
has_add_permission,
get_actions,
formfield_for_foreignkey
get_queryset
Etc are the built-in ModelAdmin functions which I am using in almost all the ModelsAdmin's in my project.
How to create a common/reusable class which include these functions and reuse in the other ModelAdmin classes?
How to pass the reference of the Model or the ModelAdmin class to the common class?
What should be the file structure to be maintained, in case this has to be used for many apps in a project.
Some direction will be great help.
Thanks.
I'd recommend using Python's class's multiple inheritance to achieve this. It would look something like this:
from django.contrib import admin
from .models import Banana
class MyAdminMixin:
def has_delete_permission(self, obj):
# Your code goes here
def has_add_permission(self, obj):
# Your code goes here
class BananaModelAdmin(MyAdminMixin, admin.ModelAdmin):
# Now your admin has access to the methods in MyAdminMixin
model = Banana
For some more information on using mixins with class-based views, take a look at the Django docs on the subject. Hope this helps!

In the Django generic detail view where does context's index come from?

In the example below where does context's index 'book_list' comes from, if is arbitrary what is the naming convention?
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
The naming convention you're referring to (_list) is based on the ListView's template_name_suffix. That inherits from MultipleObjectTemplateResponseMixin.
In practice if you use a ListView like this one based on your example:
class PublisherList(ListView):
model = Publisher
...you would able to refer to publisher_list in your template for the queryset of all publishers.
In your example you're including a list of all the books in your database using the same naming convention, but you could call that context variable (book_list) anything you wanted.
In that example, the variable name book_list is arbitrary. You could use books or anything else you like instead.
Using book_list is consistent with the ListView, which makes the list available in the template context as <lowercase model name>_list . See the docs on making friendly template contexts for more info.

create custom methods in django class base views

I want to use generic class base views using django 1.9
What i am trying to understand that
from django.views.generic import CreateView
from braces.views import LoginRequiredMixin
from .models import Invoice
class InvoiceCreateView(LoginRequiredMixin,CreateView):
model = Invoice
def generate_invoice(self):
...
return invoice
now i want to bind this custom method to url. How can i achive this?
I know using function base view its simple but i want to do this using class base views.
Help will be appreciated.
Yes, this is the main issue to grasp in CBV: when things run, what is the order of execution (see http://lukeplant.me.uk/blog/posts/djangos-cbvs-were-a-mistake/).
In a nutshell, every class based view has an order of running things, each with it's own method.
CBV have a dedicated method for each step of execution.
You would call your custom method from the method that runs the step where you want to call your custom method from. If you, say, want to run your method after the view found that the form is valid, you do something like this:
Class InvoiceCreateView(LoginRequiredMixin,CreateView):
model = Invoice
def generate_invoice(self):
... do something with self.object
return invoice
def form_valid(self,form):
self.object = form.save()
self.generate_invoice()
return super(InvoiceCreateView,self).form_valid(form)
So you have to decide where your custom method should run, and define your own method on top of the view generic method for this step.
How do you know what generic method is used for each step of executing the view? That the method the view calls when it gets the initial data for the form is def get_initial? From the django docs, and https://ccbv.co.uk/.
It looks complex, but you actually have to write very few methods, just where you need to add your own behaviour.

extra_context function for simple generic view in django

I have my page where I have my posts list, and I also want to have sidebar with suggestions. I used generic ListView for my posts, and needed to pass suggestions somehow so I used extra_context which should(?) work like that according to few examples I've read, but in template there is no 'suggestions' object.
class PostList(generic.ListView):
model = models.Post
paginate_by = 10
context_object_name = 'mj'
def get_queryset(self):
return models.Post.objects.filter(user = self.request.user)
def extra_context(self):
return {'suggestions':models.Pla.objects}
I don't have experience in django so maybe there is better way to pass suggestions for sidebar. Maybe it's possible to do this with wrapping view function since I want to have suggestions..
Class-based views don't use extra_context the way the older function-based generic views did. Instead, the usual way to do this is with a custom get_context_data call as shown in the docs:
https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-display/#adding-extra-context
The example in the docs is almost exactly what you're trying to do. You may want to follow its example further and pass in a queryset (models.Pla.objects.all()) rather than the manager object (models.Pla.objects).

How to manipulate form fields in Django dynamically within ModelAdmin?

I have a field (slug) that is "required" in the model, but want to change the field in the ModelAdmin class to be optional. If the user doesn't fill it in, it is automatically filled in by another field (name).
class SomeModel(model.Model):
name = model.CharField(max_length=255)
slug = model.SlugField(unique=True, max_length=255)
I tried to do this various ways, such as overriding get_form() within ModelAdmin or using the ModelForm class and specifying the form specifically.
class SomeModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(self.__class__, self).get_form(request, obj, **kwargs)
form.slug.required = False
return form
However, neither solution worked for me. Beyond manually creating the form, is there any other quicker solution?
I have a lot of these forms, and doing it by hand might be tedious and hard to maintain.
Found this page through Google when wrestling with the same problem myself. The following will also work in the ModelAdmin:
def get_form(self, *args, **kwargs):
form = super(SomeModelAdmin, self).get_form(*args, **kwargs)
form.base_fields['slug'].required = False
return form
Subsequent forms created from the updated ModelFormMetaclass will have the slug field unrequired.
This works better in my situation, where I have only a single class in which I need to unrequire the field, and don't need to do any data transformation on save. GoogleDroid's solution is better if you have a lot of classes, or where the data transformations are necessary.
In your get_form method, form.fields['slug'].required should work.
But the proper way to do this is to simply provide a custom ModelForm.
class SomeModelForm(forms.ModelForm):
slug = forms.CharField(required=False)
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
Incidentally, please don't do super(self.__class__, self). You should always explicitly name the current class when using super, otherwise any subclass that inherits from yours and in turn calls super will break.
Edit form.fields, not forms.fields.
By saying self.__class__, you are explicitly stopping Python from working out the inheritance - because it always refers to the concrete class - ie the bottom of the inheritance tree. But if your method is the middle of that tree, then referring to the concrete class in super is wrong - because you want it to call the next level up from where you are, not one up from the bottom. That's why you should always name the class you're in - in this case, super(SomeModelAdmin, self).
I just wanted to report back in case others might find this useful.
I was never able to in get_form method do form.fields['slug'].required and never figured out why. However, I solved my problem by creating a new form inheriting from ModelForm.
I had to override init() to set self.fields['slug'].required = False after calling the parent constructor, then override clean_slug() to modify the slug field content if required by accessing self.data['slug'].
Hope this helps someone

Categories