The order of inherited classes matters in Python? - python

I came across this error in my django application after hitting submit on a create or edit form:
No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model..
This was confusing because I have a get_success_url passed down through inheritance. To be clear, I have found the issue, but have no earthly idea why my solution worked.
Here was the code causing the error inside
.../views.py:
class FormViews():
model = Ticket
form_class = TicketForm
def get_success_url(self):
return reverse('tickets:index')
class TicketCreate(CreateView, FormViews):
template_name = 'tickets/ticket_create_form.html'
model = Ticket
form_class = TicketForm
class TicketUpdate(UpdateView, FormViews):
model = Ticket
form_class = TicketForm
template_name_suffix = '_update_form'
I created the FormViews class so there would not be any repeated code for the model, form_class, and get_success_url.
I was able to resolve this error by switching the parameters in my function definitions:
class TicketCreate(CreateView, FormViews) became class TicketCreate(FormViews, CreateView)
class TicketUpdate(UpdateView, FormViews) became class TicketUpdate(FormViews, UpdateView)
This fixed it. Now I redirect to the index page without any issues. Why is it that the get_success_url is recognized after switching the listed parent classes? I would have thought that the attributes and functions are inherited and recognized by Django regardless of order. Is this a Python or Django related issue?

In python every class has something called an MRO (Method Resolution Order), this explains it pretty well. Your FormViews (also for the most part classes in python are singular) is more of a mixin, I would call it as such: FormViewMixin.
Since CreateView and UpdateView are proper classes that have get_success_url defined, the order ABSOLUTELY matters. So I would put the things you want "discovered", first.
class TicketCreateView(FormViewMixin, CreateView):
...
is what you want.

Related

function has no attribute all

i am getting this attribute error (function has no attribute all) when i clicked on a question in my browser after running my server. I have gone through the code many times but couldn't find the error.
View.py
class IndexView(generic.ListView):
template_name ='pulls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""
Return the last five published questions (not including those set to be
published in the future).
"""
return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
#Detail Function
class DetailView(generic.DetailView):
model = Question
template_name ='pulls/detail.html'
def queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
Based on the doc, the generic.DetailView inherits methods and attributes from 5 views, including SingleObjectTemplateResponseMixin and BaseDetailView.
BaseDetailView is a base view for displaying a single object... .
So, if you are showing more than one object it is better that do not use DetailView. Otherwise, as it is written in the code, you should override self.get_object().

Django Views.py: kwargs and serializer w/o explicit reference in class definition

I'm following along with a Django Rest Framework tutorial (source code here) and I have a few questions about the below code snippet:
class ReviewCreate(generics.CreateAPIView):
serializer_class = ReviewSerializer
permission_classes = [IsAuthenticated]
throttle_classes = [ReviewCreateThrottle]
def get_queryset(self):
return Review.objects.all()
def perform_create(self, serializer):
pk = self.kwargs.get('pk')
watchlist = WatchList.objects.get(pk=pk)
review_user = self.request.user
review_queryset = Review.objects.filter(watchlist=watchlist, review_user=review_user)
if review_queryset.exists():
raise ValidationError("You have already reviewed this movie!")
if watchlist.number_rating == 0:
watchlist.avg_rating = serializer.validated_data['rating']
else:
watchlist.avg_rating = (watchlist.avg_rating + serializer.validated_data['rating'])/2
watchlist.number_rating = watchlist.number_rating + 1
watchlist.save()
serializer.save(watchlist=watchlist, review_user=review_user)
In the class definition, the variable serializer_class is declared; however in the perform_create method, serializer is an argument. Given the differences in naming, how are these two related?
In the method perform_create, self.kwargs is referenced. However, I don't see a kwargs argument passed to any __init__ method or else attached to the class object. How/where is kwargs passed to the class?
In both cases, I can only assume that the inherited class (generics.CreateAPIView) has an __init__ method that assigns a serializer_class variable to serializer. How it "listens" for a child class definition of serializer_class, I have no idea. And as for kwargs, I'm at a loss for how this is passed to the child class w/o explicitly calling defining it in its arguments.
Edit, this question Kwargs in Django does not answer my question-- it just explains what keyword arguments are. I'm not confused about their name, I'm confused by their invisible yet implicit reference in this code.
Answering your first point, we have to note two things:
First, the method perform_create is used in the create method associated to CreateModelMixin (see https://github.com/encode/django-rest-framework/blob/71e6c30034a1dd35a39ca74f86c371713e762c79/rest_framework/mixins.py#L16). The class CreateAPIView inherits from this mixin and also from GenericAPIView(See https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L184). As you can see, the create method mentioned above uses the class perform_create method and needs a serializer there. Defining perform_create without that argument would lead to an error when creating objects with this method.
Another thing to note is that the serializer used comes from the get_serializer method. Checking the source code for GenericAPIView (https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L103) we can see that this method calls get_serializer_class which retrieves the serializer defined by serializer_class.
In conclusion, if you don't modify anything else, the serializer that will be passed as a parameter will be an instance of you serializer class defined in serializer_class.
Getting to your second point, if you try to search the parent class of GenericAPIView and follow on searching the base class from which these classes inherit, you will end up finding that the base class is View from django.views.generic. There you will find in the setup method (https://github.com/django/django/blob/27aa7035f57f0db30b6632e4274e18b430906799/django/views/generic/base.py#L124) where the kwargs attribute is initialized. Also you can see in this method's code documentation the following statement:
"""Initialize attributes shared by all view methods."""
Thus in any view we create (if it has View as its base class) we will always be able to manipulate self.request, self.args and self.kwargs. I hope I explained myself clearly!

django - Creating custom queryset for RelatedManager

I tried about 25 Stackoverflow links and nobody seems to have a working solution for this problem.
I created a custom queryset like this:
class ProjectQuerySet(models.QuerySet):
def get_active(self):
from apps.projectmanagement.models import Project
return self.filter(active=True)
class ProjectManager(models.Manager):
def get_queryset(self):
return ProjectQuerySet(self.model, using=self._db)
This works great if I start at the model like
Project.objects.get_active()
But if I want to use it in a relation, no luck so far:
employee.projects.get_active()
I always get this error:
AttributeError: 'ManyRelatedManager' object has no attribute 'get_active'
What I've tried so far:
I read that use_for_related_fields = True in the manager class is deprecated. Does not work anyway on django v2.1
Adding this in my model, as half the internet states:
class Project(models.Model):
...
objects = ProjectManager()
class Meta:
base_manager_name = 'objects'
Trying to avoid the RelatedManager and to work with a queryset:
employee.projects.all().get_active()
Any ideas what I've been doing wrong? And how would I solve this? Can't be too hard, right?
As the docs state, "Base managers aren’t used when querying on related models". The example they give is for going in the other direction, ie project.employee.
There is no way to do this using managers themselves. The best bet is to define a method on your model:
class Employee(models.Model):
...
def get_active_projects(self):
return self.projects.filter(active=True)
# or
return Project.objects.filter(employee=self).get_active()

Django REST Framework - pass model to ViewSet via router

I'm trying to create a REST-ModelViewSet that has no model predefined, but takes a model when registered with the router. I need this to dynamically add models to my REST-API, without configuring any new viewsets or serializers.
My idea was to pass the model in the kwargs of __init__, but I can't figure out how to correctly do this. Here is what I tried:
//Viewset
class ThemeViewSet(viewsets.ModelViewSet):
def __init__(self, **kwargs):
self.model = kwargs['model']
self.serializer_class = None
super(ThemeViewSet, self).__init__(**kwargs)
def get_serializer_class(self):
if self.serializer_class is not None:
return self.serializer_class
class ThemeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = self.model
self.serializer_class = ThemeSerializer
return self.serializer_class
//Router:
router = routers.DefaultRouter()
router.register(r'mytheme', ThemeViewSet(model=mytheme), base_name='mytheme')
Now, if I try to print self.model in __init__, it correctly shows <class 'myapp.models.mytheme'> in the console, but Django still returns an error:
AttributeError at /api/mytheme/
This method is available only on the view class.
This error is raised by the classonlymethod-decorator. I don't really know what to make of this, is there any way to pass the model to __init__, or is there a different approach that I can try?
(I know that wq.db.rest has a router that does what I want, but I don't want to use wq. I haven't tried tastypie, would that make it easier/possible?)
Thanks in advance!
Django REST Framework expects that a ViewSet class is passed into the router, not a view instance. This is because the instance has to be created for each request, which prevents a lot of ugly issues with shared state and also follows the standard Django class-based views.
You may have better luck with having a method that creates a customized ViewSet class based on the model that is passed into it:
class ThemeViewSet(viewsets.ModelViewSet):
#classmethod
def create_custom(cls, **kwargs):
class CustomViewSet(cls):
model = kwargs["model"]
queryset = kwargs["model"].objects.all()
return CustomViewSet
Note that I'm also setting the queryset for the view, and DRF no longer accepts just a model since 2.4 was released.
This will create a new class each time it is called, and the model will automatically be set to the model that is passed into it. When registering it with the router, you would do something like:
router.register(r'mytheme', ThemeViewSet.create_custom(model=mytheme), base_name='mytheme')
This way you will still be passing a ViewSet class to the router, but it will be customized for the model that is passed in. You must make sure to set the base_name, or the router won't be able to generate the view names and you will eventually run into errors.

Django ModelForm Meta

I want create a ModelForm class where model is a parameter passed from the view.(i want a dynamic form, so i can create all forms using the same class ObjectForm by just changing model value in Meta) :
class ObjectForm(ModelForm):
model_name = None
def __init__(self, *args, **kwargs):
model_name = kwargs.pop('model_name ')
super(ModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.get_model('core', model_name )
exclude = ("societe")
An error is occured and say that model_name is not a global field.
Please help me on this problem.
your problem is that the class (and the Meta class) are processed at compile time, not when you instantiate your ObjectForm. at compile time, the model name is unknown. creating classes dynamically is possible, but a bit more complicated. as luck has it, the django devs have done the hard work for you:
>>> from django.forms.models import modelform_factory
>>> modelform_factory(MyModel)
<class 'django.forms.models.MyModelForm'>
update
So you want something like
def my_view(request):
# ...
MyForm = modelform_factory(MyModel)
form = MyForm(request.POST) # or however you would use a 'regular' form
Well, your basic error is that you are accessing model_name as a local variable, rather than as a model instance. That's fairly basic Python.
But even once you've fixed this, it still wouldn't work. The Meta class is evaluated at define time, by the form metaclass, rather than at runtime. You need to call forms.models.modelform_factory - you can pass in your modelform subclass to the factory, if you want to define some standard validation and/or fields.
form_class = modelform_factory(MyModel, form=MyModelForm)

Categories