Can not understand Django and mixin behaviour - python

I have the following hierarchy of classes:
class ProfileUpdateView( UpdateView, LoggerMixin ):
def get_context_data(self, **kwargs):
context = super(ProfileCreateView, self).get_context_data(**kwargs)
...
return context
UpdateView is in fact django.views.generic.UpdateView
class EventViewMixin(object):
template_name = ...
model = Event
form_class = ...
def get_success_url(self):
return self.success_url + str(self.object.id)
Class UpdateEventView mixes ProfileUpdateView and EventViewMixin
class UpdateEventView(ProfileUpdateView, EventViewMixin):
def form_valid(self, form):
...
return super(UpdateEventView, self).form_valid(form)
The problem in that for some reason the field "model=Event" is not visible to the framework when
it tries to use UpdateEventView. So I get the error:
UpdateEventView is missing a queryset. Define UpdateEventView.model, UpdateEventView.queryset, or override UpdateEventView.get_object().
What am I missing?
DISCLAIMER: I'm sort of a newbie to Python/Django.

So my question in sort of dumb ...
The problem is in the order of the mixins:
class UpdateEventView(ProfileUpdateView, EventViewMixin):
....
must be replaced with:
class UpdateEventView( EventViewMixin, ProfileUpdateView ):
This is because ProfileUpdateView has in its inheritance tree a field "model=None",
and if ProfileUpdateView is on the first position in the that is the value that
will be considered. If EventViewMixin comes first, then the correct value is taken.

Related

Adding Validation to Django Inlineformset Causing Metaclass Conflict

I am trying to add validation to my Django forms.
I want to require at least one child model for my inlineformset.
I am using the following as a reference:
Inline Form Validation in Django
I am still getting an error
line 454, in formset_factory
return type(form.name + str('FormSet'), (formset,), attrs) TypeError: metaclass conflict: the metaclass of a derived class must
be a (non-strict) subclass of the metaclasses of all its bases
from django.forms import inlineformset_factory
from .models import *
from django.forms.models import BaseInlineFormSet
from django import forms
class PropertyForm(forms.ModelForm):
class Meta:
model = Property
exclude = ()
class UnitForm(forms.ModelForm):
class Meta:
model = Unit
exclude = ()
class CustomUnitFormset(BaseInlineFormSet):
def is_valid(self):
return super(CustomUnitFormset, self).is_valid() and \
not any([bool(e) for e in self.errors])
def clean(self):
# get forms that actually have valid data
count = 0
for form in self.forms:
try:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
count += 1
except AttributeError:
pass
if count < 1:
raise forms.ValidationError('You must have at least one unit')
UnitFormSet = inlineformset_factory(Property, Unit, form=PropertyForm,
formset='CustomUnitFormset',
extra=0,
min_num=1,
validate_min=True,
validate_max=True,
max_num=10,
)
view.py
class PropertyUnitCreate(LoginRequiredMixin, CreateView):
model = Property
fields = ['address']
success_url = reverse_lazy('property-list')
def get_context_data(self, **kwargs):
data = super(PropertyUnitCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['units'] = UnitFormSet(self.request.POST)
else:
data['units'] = UnitFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
units = context['units']
form.instance.user = self.request.user
with transaction.atomic():
self.object = form.save()
if units.is_valid():
units.instance = self.object
units.save()
return super(PropertyUnitCreate, self).form_valid(form)
I have also tried to understand the inheritance issues explained here:Triple inheritance causes metaclass conflict... Sometimes
My view logic comes from https://medium.com/#adandan01/django-inline-formsets-example-mybook-420cc4b6225d
But I dont see where i have a class inheriting from two classes that would cause this error. Everything works fine if i remove the " formset='CustomUnitFormset',
"
Can someone explain the reason for the error and how i could prevent this? Could i be managing my forms.py file better?
You need to pass the actual formset class, not a string, to the factory.
UnitFormSet = inlineformset_factory(Property, Unit, form=PropertyForm,
formset=CustomUnitFormset, ...)

Provide different kwargs for each form in formset

I am trying to use a formset to create forms for a set of timeframes related to dates:
class Event(models.Model):
date = models.DateField()
class TimeFrame(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
event = models.ForeignKey('Event')
I have code that gets me a queryset of timeframes for each event and added a kwarg to pass this into my form:
class SelectDatesForm(forms.Form):
timeframes = forms.ModelChoiceField(queryset=HostTimeFrame.objects.none())
def __init__(self, *args, **kwargs):
qs = kwargs.pop('timeframes')
super(SelectDatesForm, self).__init__(*args, **kwargs)
self.fields['timeframes'].queryset = qs
Now I'm trying to construct a formset that lets me show timeframes for multiple events on one page. I already found this question, explaining how to pass initial data, for serveral forms, but its not the same as passing it to a queryset.
Also there is this new function from django 1.9 but it doesnt allow me to get different querysets for each form.
UPDATE:
I got the solution from the answer working, however, whenever im running formset.is_valid() i get the error:
Select a valid choice. That choice is not one of the available
choices.
Here is what I do in my view:
timeframes = [HostTimeFrame.objects.all()]
SelectDatesFormset = formset_factory(form=SelectDatesForm, extra=len(timeframes), formset=BaseSelectDatesFormSet)
if request.method == 'POST':
formset = SelectDatesFormset(request.POST, form_kwargs={'timeframes_list': timeframes})
if formset.is_valid():
# do something with the formset.cleaned_data
print(formset)
pass
else:
formset = SelectDatesFormset(form_kwargs={'timeframes_list': timeframes})
Ive been trying for hours to find where this actual validation is done, but i cant find it for the live of me.
Edit: I tested this with the singular form, and i have the same issue, I created a new question for this here.
UPDATE:
Only partial solution, see question.
Solved it myself:
First I created a BaseFormSet:
class BaseSelectDatesFormSet(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super(BaseSelectDatesFormSet, self).get_form_kwargs(index)
kwargs['timeframes'] = kwargs['timeframes_list'][index]
return kwargs
Then I could pass the list of timeframes in the view:
SelectDatesFormset = formset_factory(form=SelectDatesForm, extra=4, formset=BaseSelectDatesFormSet)
formset = SelectDatesFormset(form_kwargs={'timeframes_list': timeframes})
Finally I had to update my form init to pop the list as well so the super constructor doesnt complain about unwanted kwargs:
def __init__(self, *args, **kwargs):
qs = kwargs.pop('timeframes')
qs_list = kwargs.pop('timeframes_list')
super(SelectDatesForm, self).__init__(*args, **kwargs)
self.fields['timeframes'].queryset = qs.order_by('start')
For peeps using Class Based View FormView along with form_class as formset or formset_factory, they can add an extra attribute as follows:
Pass form_kwargs in the get_form method by overriding it.
timeframes = [HostTimeFrame.objects.all()]
class SelectDatesView(FormView):
form_class = formset_factory(form=SelectDatesForm, extra=len(timeframes)
def get_form(self, form_class=None):
"""Override the method to add form kwargs. Returns an instance of the form to be used in this view."""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs(), form_kwargs={"timeframes": timeframes})
One can access it directly in the __init__ method's kwargs.
def __init__(self, *args, **kwargs):
super(SelectDatesForm, self).__init__(*args, **kwargs)
qs = kwargs.get('timeframes')
self.fields['timeframes'].queryset = qs.order_by('start')

How can I return regular response in override get method, Django

I try to use class based views in Django. And I have such problem: I define a base class for a blog (BlogBaseView) and two other classes, that inherit it.
And in the second class(BlogIndexView) I want to make the search by get request, so I have override get method. It works, but if I don't make get request, it returns HttpResponse, however I want to return usual context (which BlogIndexView retunes without override get method).
What can I do?
class BlogBaseView(View):
def get_context_data(self, **kwargs):
context = super(BlogBaseView, self).get_context_data(**kwargs)
blog_categories = []
categories = BlogCategory.objects.all()
for category in categories:
blog_categories.append(tuple([category, category.get_number_of_category_items]))
context['name_page'] = 'blog'
context['tags'] = Tag.objects.all()
context['blog_categories'] = blog_categories
return context
class BlogIndexView(BlogBaseView, ListView):
queryset = Post.objects.all().order_by('-date_create')
template_name = 'index_blog.html'
context_object_name = 'posts'
def get(self, request):
if request.GET.get('tag'):
context = {
'posts' : Post.objects.filter(tags__name__in=[request.GET.get('tag')])
}
return render(request, self.template_name, context)
return HttpResponse('result')
class BlogFullPostView(BlogBaseView, DetailView):
model = Post
template_name = 'full_post.html'
pk_url_kwarg = 'post_id'
context_object_name = 'post'
Thanks!
ListView class also has a get_context_data method, so you should override that instead of get method. Using super you'll get access to BlogBaseView.get_context_data and then you can extended the result.
Here's how:
class BlogIndexView(BlogBaseView, ListView):
queryset = Post.objects.all().order_by('-date_create')
template_name = 'index_blog.html'
context_object_name = 'posts'
def get_context_data(self, **kwargs):
# use the (super) force Luke
context = super(BlogIndexView, self).get_context_data(**kwargs)
if self.request.GET.get('tag'):
context['posts'] = Post.objects.filter(tags__name__in=[self.request.GET.get('tag')])
return context
If you are overriding ListView then it's not a good idea to override the get method, as you will lose a lot of the ListView functionality.
In this case, it would be a better idea to override get_queryset, and do the search there.
def get_queryset(self):
queryset = super(BlogIndexView, self). get_queryset()
if request.GET.get('tag'):
queryset = queryset.filter(tags__name=request.GET['tag'])
return queryset

Django - CreateView get_context_data TypeError

I have a mixin on top of my app/views.py:
class RefereeViewMixin(object):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
if not self.request.user.is_superuser and \
not self.request.user.has_group("referee"):
raise PermissionDenied
return super(RefereeViewMixin, self).dispatch(*args, **kwargs)
And also a view which is derived from my custom mixin and CreateView:
class RefereeResultCreateView(RefereeViewMixin, CreateView):
category = None
template_name = "referee/result_create.html"
fields = ["minutes", "seconds", "miliseconds", "disqualification"]
def get_success_url(self):
return reverse("order_list", args=[self.category])
def get_context_data(self, **kwargs):
context = super(RefereeResultCreateView, self).get_context_data(
**kwargs)
context["project"] = Project.objects.get(pk=self.kwargs.get("pk"))
return context
def form_valid(self, form):
result = form.save(commit=False)
result.project = Project.objects.get(pk=self.kwargs.get("pk"))
result.save()
messages.success(self.request, _(
"Result entry for {} project created.".format(result.name)))
return super(RefereeResultCreateView, self).form_valid(form)
Finally, my final view which is derived from RefereeResultCreateView:
class RefereeFireFighterResultCreateView(RefereeResultCreateView):
model = FireFighterResult
category = "fire_fighter"
fields = RefereeResultCreateView.fields + [
"extinguish_success", "extinguish_failure", "wall_hit"]
And the pattern is like that:
url(r'^fire_fighter/(?P<pk>\d+)/create/$',
RefereeFireFighterResultCreateView.as_view(),
name="referee_fire_fighter_result_create")
However, when I go to /app/fire_fighter/some_id/create/ I got TypeError. It tells me super(type, obj): obj must be an instance or subtype of type. When I debug it, I see that RefereeResultCreateView's get_context_data method causes that error. If I don't override that method, I don't see any errors. When I override it at RefereeFireFighterResultCreateView class, I don't see any errors.
I can overcome the problem. However, I think it is not the elegant way. Actually, it is very dirty. Also, I am curious about the reason. Why it doesn't work in the way I expect? What is the elegant solution?
UPDATED
Here's the traceback:
https://gist.github.com/ilkerkesen/e65fefa66890758bcdcd

Validation error for one to one relationship in django modelfrom

I have 2 models which are related to each other using One to One Relation. How can i display the validation error if object of one model is already related to another.
Suppose i have 2 models
Class Place(models.Model):
field1 = CharField()
field2 = CharField()
Class Restaurant(models.Model):
related_field = OneToOneField(Place)
field3 = CharField()
In my case For one place one Restaurant. If user submits new Restaurant for the same place, then a validation should raise
If Restaurant object already attached to Place object, so for second Restaurant for same Place object i want to display validation
In admin we usually see this message "Restaurent with this Place already exists." But how can i achieve the same this using modelform with generic views
class RestaurantCreateView(CreateView):
form_class = RestaurantForm
template_name = 'restaurant_create.html'
def get_success_url(self):
return reverse_lazy('restuarants', args=[self.object.id])
def get_place(self, **kwargs):
id = self.kwargs['pk']
place = Place.objects.get(id=id)
return place
def get_context_data(self, **kwargs):
context = super(RestaurantCreateView, self).get_context_data(**kwargs)
place = self.get_place()
context['place'] = place
return context
def form_valid(self, form):
self.object = form.save(commit=False)
place = self.get_place()
self.object.place = place
self.object.save()
return HttpResponseRedirect(self.get_success_url())
How can perform One To One relation validation in class based generic views?
Then you need to pass the Place object into your Restaurant form. The form validation has no access to these things defined in the view unless you explicitly pass it that data.
class MyForm(forms.ModelForm):
class Meta:
model = Restaurant
def __init__(self, *args, **kwargs):
self.place = kwargs.pop('place', None)
super(MyForm, self).__init__(*args, **kwargs
def clean_related_field(self):
place = self.place
try:
place.restaurant
except Restaurant.DoesNotExist:
return place
raise forms.ValidationError("Restaurant already exists for that book")
class MyView(...):
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['place'] = Place.objects.get(id=self.kwargs['pk'])
return kwargs
{% if form.related_field.errors %}.........{% endif %}
Another approach is to fake validation in form_valid() and set the errors dict, and return the response. This method may be preferable since it's much more contained. Also especially if you are not using the form field anyways, you might as well add an error flag to the context.
class MyView():
def form_valid(self, form):
if Restaurant.objects.filter(related_field=self.get_place()):
ctx = self.get_context_data(form=form)
ctx['massive_error'] = True
return self.render_to_response(ctx)

Categories