I have two models, City and State with State being a ForeignKey relation of City.My CityDetailView url is constructed as:
r'^state/(?P<state>[-\w]+)/city/(?P<slug>[-\w]+)/$'
My CityDetailView called by the above url is:
class CityDetailView(DetailView):
model = City
context_object_name = 'city'
template_name = 'location/city_detail.html'
def get_queryset(self):
state = get_object_or_404(State, slug__iexact=self.kwargs['state'])
return City.objects.filter(state=state)
def get_context_data(self, **kwargs):
context = super(CityDetailView, self).get_context_data(**kwargs)
city = City.objects.get(slug__iexact=self.kwargs['slug'])
context['guide_list'] = Guide.objects.filter(location=city).annotate(Count('review'), Avg('review__rating'))
return context
My City model has unique Names for each city. If I try and access a city that occurs in two states I get an error that the get() returned more than one City. I am trying to override the get_queryset() method to filter out only the City models in a single state but it does not seem to be working which is odd because my CityListView is similar but works fine. Any thoughts on what I am missing would be appreciated.
You need to override the method get_object in DetailView to do this.
Something like this should do:
class CityDetailView(DetailView):
model = City
context_object_name = 'city'
template_name = 'location/city_detail.html'
def get_object(self):
state = get_object_or_404(State, slug__iexact=self.kwargs['state'])
return self.model.objects.filter(state=state)
def get_context_data(self, **kwargs):
context = super(CityDetailView, self).get_context_data(**kwargs)
cities = self.object
context['guide_list'] = Guide.objects.filter(location=cities).annotate(Count('review'), Avg('review__rating'))
return context
I was getting the error on the get_context_data function because I was not filtering the city list there not on the primary view object.
Related
So I have two Models that I want to relate with a ForeignKey. One of the ModelForms I want to have it's Foreign Key field pre populated before the model gets created. The info from the ForeignKey comes from a ListView (List of Cars that belong to clients) template.
MODELS.PY
class ClientCar(models.Model):
license_plate = models.CharField(max_length=20, unique=True, name='license_plate')
def__str__:
pk = self.pk
license_plate = self.license_plate
return f"pk:{pk} license_plate {license_plate}"
class CarDetail(model.Model):
car = models.ForeignKey(ClientCar, on_delete=models.CASCADE, null=False)
detail = models.CharField(max_length=40, null=False)
So the ListView template will have the basic crud of the Car model but I also want to add a "Wash button", the wash button will pass the selected Car's pk to the CarDetail Form template. It is here where I am having issues. I can Query the PK of the car from Kwargs but I can't seem to populate the Form's field with that query or have it render on the template.
VIEWS.PY
class WashService(LoginRequiredMixin, CreateView):
model = CarDetail
form_class = WashServiceForm
template_name = 'service_app/standard_wash_form.html'
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(WashService, self).get_form_kwargs(*args, **kwargs)
ctd = ClientCar.objects.filter(pk=self.kwargs.get('pk')).values('license_plate')
kwargs['initial']['car'] = ctd
return kwargs
I have researched this and came to the understanding that in the Form for creating this model I have to overwrite the _ _ init _ _ function, I'm not really sure how to solve this since I don't know how to call the kwargs passed from the Listview template from the forms.py
If you can guide me with some snippets or anything I'm greatful.
Thanks in advance.
I think it makes more sense to simply change what function the ModelChoiceField uses for the choices. We can first make a subclass of ModelChoiceField for the car, to select this by license plate:
from django import forms
class CarByLicensePlateChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.license_plate
Then in your WashServiceForm we can use this field:
class WashServiceForm(forms.ModelForm):
car = CarByLicensePlateChoiceField(queryset=Car.objects.all())
class Meta:
model = Car
fields = ['car', 'detail']
In your CreateView, you can then populate the car with the Car that belongs to the given primary key:
from django.shortcuts import get_object_or_404
class WashService(LoginRequiredMixin, CreateView):
model = CarDetail
form_class = WashServiceForm
template_name = 'service_app/standard_wash_form.html'
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
initials = kwargs.setdefault('initial', {})
intial['car'] = get_object_or_404(Car, pk=self.kwargs['pk'])
return kwargs
I sort of need help understanding my own code specifically the views.py. I'm trying to change url pattern for my TitleUpdateListView from using my Update models title field and instead using the slug field instead.
If someone could help explain line by line whats going in in my TitleUpdateListView so I could better understand whats specifically going on that would be great.
urls.py
urlpatterns = [
# Update view for each game
path('<str:title>/updates/', TitleUpdateListView.as_view(), name='title-updates'),
# Adds the ability to sort by platform
path('<str:title>/updates/<int:platform_id>/', TitleUpdateAjaxListView.as_view(), name='title-updates-ajax'),
]
views.py
class TitleUpdateListView(ListView):
model = Update
context_object_name = 'updates'
template_name = 'updates/title_updates.html'
def get_queryset(self):
title = get_object_or_404(Game, title=self.kwargs.get('title'))
return Update.objects.filter(game=title).order_by('-date_published')
def get_context_data(self, **kwargs):
context = super(TitleUpdateListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, title=self.kwargs.get('title'))
return context
class TitleUpdateAjaxListView(ListView):
model = Update
template_name = 'updates/updates_ajax.html'
context_object_name = 'updates'
paginate_by = 5
def get_queryset(self):
title = get_object_or_404(Game, title=self.kwargs.get('title'))
return Update.objects.filter(game=title, platform=Platform.objects.filter(
id=self.kwargs.get('platform_id')).first()).order_by('-date_published')
def get_context_data(self, **kwargs):
context = super(TitleUpdateAjaxListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, title=self.kwargs.get('title'))
return context
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
context = self.get_context_data()
return render(request, self.template_name, context)
Not sure what you meant by "I'm trying to change url pattern for my TitleUpdateListView from using my Update models title field and instead using the slug field instead.". In the urls.py, you can change the name of the parameter (the xxxx in <str:xxxx>) to whatever you want, as long as you also look for this same name in the view. You can change it to <str:slug> and in your view you'd fetch it like self.kwargs.get('slug'). Just remember to also change which parameter are you using to filter the Game table (slug instead of title).
As for explaining what your view does, you should probably take a look at Django's docs on Class Based Views, but I'll try to give an overview:
The get_queryset method is searching the Game table to find the games whose title matches the title passed in the URL parameter. It then returns a list of all Update objects whose game field points to the game just found.
The get_context_data method is adding the same Game object found in the get_queryset method to the view's context under the 'game' key. This means that you can access the Game object inside the template that this view renders.
You just need to change the get_queryset method of your view:
# change url variable name from title to slug
path('<str:slug>/updates/', TitleUpdateListView.as_view(), name='title-updates'),
def get_queryset(self):
# the url variables are stored in the dictionary self.kwargs
slug = self.kwargs.get('slug')
game = get_object_or_404(Game, slug=slug)
return Update.objects.filter(game=game).order_by('-date_published')
The same applies for get_context_data:
def get_context_data(self, **kwargs):
context = super(TitleUpdateListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, slug=self.kwargs.get('slug'))
return context
Say we have this Model:
class Event(models.Model):
name = models.CharField('Nome do evento', max_length=50)
code = models.CharField('Código de entrada', max_length=10)
artists_list = models.ForeignKey(ListGroup, on_delete=None,
related_name='lists_names', null=True)
and this View
class HomeView(LoginRequiredMixin, TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
context['form'] = CreateEventForm(self.request.POST or None)
context['defaultTitle'] = 'Novo Evento'
context['formTitle'] = 'Criar'
return context
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
form = context['form']
print(form)
if form.is_valid():
form.save()
return self.render_to_response(context)
and this Form
class CreateEventForm(forms.ModelForm):
class Meta:
model = Event
fields = ('name', 'code', 'artists_list',)
Everything works great, but I would like to be able to select multiple entries that my Foreign key will retrieve. So I would like to render each entry as a checkbox instead of a select. How can I achieve that? I already searched a lot and only found about general charfields, nothing about Foreing Key
Here's how its rendering
A foreign key can't point to multiple entries. If you want that, you should use a ManyToManyField.
I am looking for a way to automatically fill in the ForeignField ID based on the url. The user will click a link, pointing to the CreateView with the id of the ForeignField added to the end of it. I am unsure of the best way of doing this.
urls.py
url(r'^comment/(?P<pk>[0-9]+)/$', CommentCreate.as_view()),
views.py
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
queryset = Comment.objects.all()
html-link.html
Comment
As far as I know queryset is invalid for CreateView. Of course you can set it, but it does not make a sense, because when you use CreateView it is supposed that you want to create an object.
I guess you can try something like:
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
form_class = FollowupForm
def get_form(self):
form = super(CommentCreate, self).get_form(self.form_class)
# artical_id - is a name of foreign key defined the Comment model.
form.instance.artical_id = Artical.objects.get(pk=self.kwargs.get('pk', None))
return form
That way you can spend the article in the context also
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['artical'] = Artical.objects.get(pk=self.kwargs[artical_id])
return context
It can be corrected by doing filter like this:
context['artical'] = Artical.objects.filter(pk=self.kwargs[artical_id])
It is more suggestive if your url is like this:
#urls.py
url(r'^comment/(?P<artical_id>[0-9]+)/$', CommentCreate.as_view()),
#views.py
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
queryset = Comment.objects.all()
#That way you can spend the article in the context also
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['artical'] = Artical.objects.get(pk=self.kwargs[artical_id])
return context
I'm trying to override concept queryset in my child form, to get a custom list concepts based on the area got from request.POST, here is my list of concepts, which i need to filter based on the POST request, this lists is a fk of my child form (InvoiceDetail). is it possible to have these filters?
after doing some test when I pass the initial data as the documentation says initial=['concept'=queryset_as_dict], it always returns all the concepts, but i print the same in the view and its ok the filter, but is not ok when i render in template, so I was reading that I need to use some BaseInlineFormset. so when I test I obtained different errors:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
'InvoiceDetailFormFormSet' object has no attribute 'fields'
so here is my code:
models.py
class ConceptDetail(CreateUpdateMixin): # here, is custom list if area='default' only returns 10 rows.
name = models.CharField(max_length=150)
area = models.ForeignKey('procedure.Area')
class Invoice(ClusterableModel, CreateUpdateMixin): # parentForm
invoice = models.SlugField(max_length=15)
class InvoiceDetail(CreateUpdateMixin): # childForm
tax = models.FloatField()
concept = models.ForeignKey(ConceptDetail, null=True, blank=True) # fk to override using custom queryset
invoice = models.ForeignKey('Invoice', null=True, blank=True)
views.py
class CreateInvoiceProcedureView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = 'invoice/invoice_form.html'
model = Invoice
permission_required = 'invoice.can_check_invoice'
def post(self, request, *args, **kwargs):
self.object = None
form = InvoiceForm(request=request)
# initial initial=[{'tax': 16, }] removed
invoice_detail_form = InvoiceDetailFormSet(request.POST, instance=Invoice,
request=request)
return self.render_to_response(
self.get_context_data(
form=form,
invoice_detail_form=invoice_detail_form
)
)
forms.py
class BaseFormSetInvoice(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
# call first to retrieve kwargs values, when the class is instantiated
self.request = kwargs.pop("request")
super(BaseFormSetInvoice, self).__init__(*args, **kwargs)
self.queryset.concept = ConceptDetail.objects.filter(
Q(area__name=self.request.POST.get('area')) | Q(area__name='default')
)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ('invoice',)
class InvoiceDetailForm(forms.ModelForm):
class Meta:
model = InvoiceDetail
fields = ('concept',)
InvoiceDetailFormSet = inlineformset_factory(Invoice, InvoiceDetail,
formset=BaseFormSetInvoice,
form=InvoiceDetailForm,
extra=1)
How can i fix it?, what do i need to read to solve this problem, I tried to debug the process, i didn't find answers.
i try to do this:
def FooForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['concept'].queryset = ConceptDetail.objects.filter(area__name='default')
In a inlineformset_factory how can do it?.
After a lot of tests, my solution is override the formset before to rendering, using get_context_data.
def get_context_data(self, **kwargs):
context = super(CreateInvoiceProcedureView, self).get_context_data(**kwargs)
for form in context['invoice_detail_form']:
form.fields['concept'].queryset = ConceptDetail.objects.filter(area__name=self.request.POST.get('area'))
return context