I'm new to django framework developers, and I have read a lot of documentation of Class-Based View and Forms.
Now, I want to create a single page (for test purpose) that contains a list of cars and a Forms, at the bottom page, for create a new Car.
this is my views.py
class IndexView(ListView):
template_name = "index.html"
context_object_name = "cars"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context["form"] = CarForm
return context
def get_queryset(self):
self.brand = self.kwargs.pop("brand","")
if self.brand != "":
return Car.objects.filter(brand__iexact = self.brand)
else:
return Car.objects.all()
def post(self, request):
newCar = CarForm(request.POST)
if newCar.is_valid():
newCar.save()
return HttpResponseRedirect("")
else:
return render(request, "index.html", {"form": newCar})
class CarForm(ModelForm):
class Meta:
model = Car
delete = True
and this is a picture with what I want create.
image
My questions are:
1) this is a "Best-Pratice" for this purpose?
2) The {{ car.name.errors }} in my template are always blank (no validation error shows).
Thanks! … and sorry for my english.
You could go other way around. Create a FormView and put the list of cars in context. That way form handling becomes easier. Like this -
class CarForm(ModelForm):
class Meta:
model = Car
delete = True
class IndexView(FormView):
template_name = "index.html"
form_class = CarForm
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
# Pass the list of cars in context so that you can access it in template
context["cars"] = self.get_queryset()
return context
def get_queryset(self):
self.brand = self.kwargs.pop("brand","")
if self.brand != "":
return Car.objects.filter(brand__iexact = self.brand)
else:
return Car.objects.all()
def form_valid(self, form):
# Do what you'd do if form is valid
return super(IndexView, self).form_valid(form)
Related
I need to set input (select) value using pk sended by URL.
Using __init__ in form, almost get the answer, but __init__ method is executed twice and clean my value.
Form:
class CrearDelitoForm(forms.ModelForm):
class Meta:
model = Delito
exclude = ()
def __init__(self, numero_pk = None, *args, **kwargs):
super(CrearDelitoForm, self).__init__(*args, **kwargs)
self.fields["imputado"].queryset = Imputado.objects.filter(numero_id = numero_pk)
DelitoFormset = inlineformset_factory(
Expediente,
Delito,
form=CrearDelitoForm,
extra=1,
can_delete=True,
fields=('imputado', 'delito', 'categoria'),
}
)
Views:
class CrearDelito(CreateView):
model = Delito
form_class = CrearDelitoForm
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['formset'] = DelitoFormset()
context['expedientes'] = Expediente.objects.filter(id = self.kwargs['pk'])
return context
def get_form_kwargs(self, **kwargs):
kwargs['numero_pk'] = self.kwargs['pk']
return kwargs
**
System check identified no issues (0 silenced).
June 08, 2020 - 11:33:57
Django version 2.2.12, using settings 'red.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
, ]>
**
I think problem is on context = super().get_context_data(**kwargs) but don't know why.
A CreateView makes a form, the form you specified in the form_class. It will furthermore pass the request.POST and request.FILES to the form, you thus do not have to construct a formset yourself, but let the CreateView do that.
You need to pass the dictionary to the form_kwargs to the form_kwargs of the formset, so the get_form_kwargs needs to be altered to:
class CrearDelito(CreateView):
model = Delito
form_class = DelitoFormset
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['expedientes'] = Expediente.objects.filter(id=self.kwargs['pk'])
context['formset'] = context['form']
return context
def get_form_kwargs(self, **kwargs):
form_kwargs = super().get_form_kwargs(**kwargs)
form_kwargs['form_kwargs'] = {'numero_pk': self.kwargs['pk']}
form_kwargs['instance'] = Expediente.objects.get(id=self.kwargs['pk'])
return form_kwargs
def get_success_url(self):
return reverse('repositorio:crear_victima', args=[request.POST.get('numero_id')])
The following code is working but I wonder if there is a more elegant way of doing. I have to pass the specie_id so I can filter the breeds to the corresponding specie. I can pass the specie_id to the view but I also have the information in the Resident model ("specie").
both get() and post() have nearly the same code, passing the specie_id.
The View
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(instance=initial, specie_id=initial.specie.id)
return render(request, self.template_name, {"form": form})
def post(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(request.POST, specie_id=initial.specie.id, instance=initial)
if form.is_valid():
form.save()
return redirect("resident_detail", pk)
return render(request, self.template_name, {"form", form})
The Form
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
"specie": HiddenInput(),
}
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
EDIT :
#Alasdair's answer is good and I think I perfected it a little more. My form is used for the create view too. So I added a check to see if I have the specie_id in kwargs (create) or if I have to use the specie_id from the instance (update)
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentForm, self).__init__(*args, **kwargs)
if not self.specie_id:
self.specie_id = self.instance.specie.id
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
It looks like you can do self.fields["breed"].queryset = Breed.objects.for_specie(self.initial.specie_id), then you don't need to pass in specie_id to the form.
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
}
def __init__(self, *args, **kwargs):
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["breed"].queryset = Breed.objects.for_specie(self.instance.specie_id)
Note I've removed the specie hidden input above, I don't think it's necessary.
The UpdateView takes care of passing instance to the form, so you can simplify the view.
from django.urls import reverse
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get_success_url(self):
"""Redirect to resident_detail view after a successful update"""
return reverse('resident_detail', args=[self.kwargs['pk']]
I think a better approach would be overriding the get_form_kwargs method in your views.
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
form_kwargs.update({'specie_id': self.kwargs.get('specie_id')})
return form_kwargs
I want to put info about one object in many views without repeating it in get_context_data in each view. As u understand i need a class with get_context_data inside, that i can mix with other views.
Here in my example i want to see 'some_object' in context of UpdateAnotherObjectView:
class BaseObjectInfoView(View):
def get_context_data(self, **kwargs):
context_data = super(BaseObjectInfoView, self).get_context_data(**kwargs)
context_data['some_object'] = SomeObjects.objects.get(pk=1)
return context_data
class UpdateAnotherObjectView(BaseObjectInfo, UpdateView):
template_name = 'create_object.html'
form_class = AnotherObjectForm
model = AnotherObjects
def get_context_data(self, **kwargs):
context_data = super(UpdateAnotherObjectView, self).get_context_data(**kwargs)
context_data['all_another_objects'] = AnotherObjects.objects.all()
return context_data
it works, but get_context_data is not a part of parent 'View' class. May be i need more special class to inherit from in BaseObjectInfoView?
or maybe better to construct context with another method ?
Mixins don't need to be views, but it helps IDE's if they have the methods they're overriding.
Contexts are handled by django.views.generic.base.ContextMixin (details on this very handy site), So the class-based views way of things would be this:
from django.views import generic
class WebsiteCommonMixin(generic.base.ContextMixin):
page_title = ''
active_menu = None
def get_context_data(self, **kwargs):
context = super(WebsiteCommonMixin, self).get_context_data(**kwargs)
context.update(dict(page_title=self.page_title, active_menu=self.active_menu))
return context
class NewsListView(WebsiteCommonMixin, ListView):
page_title = 'News list'
active_menu = 'News'
model = News
paginate_by = 12
I do this for many projects and the simple views you have to create anyway, are fully declarative. And by simple, I mean that they can consist of mulitple mixins, all doing the hard stuff in either get_queryset, get_context_data or form_valid. More elaborate example, straight from a project:
class FeedbackMixin(object):
message = 'Well Done!'
def __init__(self):
self._message_kwargs = {}
super().__init__()
def add_message_kwarg(self, name, value) -> None:
self._message_kwargs[name] = value
def format_message(self, kwargs) -> str:
return self.message.format(**kwargs)
def generate_message(self) -> None:
msg = self.format_message(self._message_kwargs)
messages.success(getattr(self, 'request'), msg)
class ModelFeedbackMixin(FeedbackMixin, generic.edit.ModelFormMixin):
success_view_name = None
success_url_kwargs = None
def get_success_url_kwargs(self):
return self.success_url_kwargs
def get_success_url(self) -> str:
success_url_kwargs = self.get_success_url_kwargs()
if not self.success_view_name:
url = super().get_success_url()
elif success_url_kwargs is not None:
url = reverse(self.success_view_name, kwargs=success_url_kwargs)
else:
if hasattr(self.object, 'slug'):
url_kwargs = {'slug': self.object.slug}
else:
url_kwargs = {'pk': self.object.pk}
url = reverse(self.success_view_name, kwargs=url_kwargs)
return url
def form_valid(self, form):
response = super().form_valid(form)
self.generate_message()
return response
Maybe this way could be easier to read...
def add_context(func):
# this is a wrapper function
def wrapper(*args, **kwargs):
context_data = func(*args, **kwargs)
context_data['some_object'] = SomeObjects.objects.get(pk=1)
return context_data
return wrapper
class UpdateAnotherObjectView(BaseObjectInfo, UpdateView):
template_name = 'create_object.html'
form_class = AnotherObjectForm
model = AnotherObjects
#add_context
def get_context_data(self, **kwargs):
kwargs['all_another_objects'] = AnotherObjects.objects.all()
return kwargs
I want my user to be able to see a page, update that page, and then be returned to that page or continue making more edits.
Here's the view to show the information:
# urls.py
url(r'^gameview/$', views.GameView.as_view(template_name='matchview.html'), name='GameView')
# Views.py
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
So now they want to add information about a game. I use an UpdateView
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
When the user finishes updating in the latter view, they should be returned to the former on the exact same team. This post helped me move in the right direction insofar as using 'get_success_url', but I'm still stuck because I don't think I'm using kwargs. Everything I've tried has resulted in errors.
(My (gulp) thought is that I should re-write the urls to use PKs so that this stuff is easier, but I wanted to make sure)
EDIT:
My fugly attempt (Note: I Have two submit buttons, one to update and one to update and add notes).
def form_valid(self, form):
if form.is_valid():
form.save()
if 'submit' in self.request.POST:
q = self.request.GET.get('match')
return reverse_lazy('TeamView', args=(q))
else:
return render('addnotes', {'game' : q})
SOLUTION:
Learned how to use URL Parameters and kwargs:
(for anyone new like me, self.kwargs.get is brilliant)
def get_success_url(self, **kwargs):
q = self.kwargs.get('match')
if "submit" in self.request.POST:
url = reverse('GameView', args={q : 'match'})
else:
url = reverse('AddNotes', args={q : 'match'})
return url
What about get_absolute_url for the model object?
https://docs.djangoproject.com/en/1.10/ref/models/instances/#get-absolute-url
from django.urls import reverse
class GameModel(models.Model):
....
def get_absolute_url(self):
return reverse('game:single_page', args=[str(self.id)])
And in your GameView:
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
....
def get_success_url(self, **kwargs):
return self.object.get_absolute_url()
I have two similar classes, query filter is county code DE or NL.
Is possible to make a objects filter on base of url name and keep only one class? For example, if i point my browser to
127.0.0.1:8000/germany
django will call to filter
feed__country__name='DE'
and
127.0.0.1:8000/netherland
will use
feed__country__name='NL'?
My URL:
url(r'^netherland/$', NLFeedList.as_view(), name='nl'),
url(r'^germany/$', DEFeedList.as_view(), name='de'),
VIEWS:
class NLFeedList(PaginationMixin, ListView):
model = FeedItem
template_name = 'nl_feed.html'
context_object_name = 'feed_items'
paginate_by = 20
def get_queryset(self):
items = FeedItem.objects.filter(feed__country__name='NL')
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(NLFeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name='NL')
return context
class DEFeedList(PaginationMixin, ListView):
model = FeedItem
template_name = 'de_feed.html'
context_object_name = 'feed_items'
def get_queryset(self):
items = FeedItem.objects.filter(feed__country__name='DE')
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(DEFeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name='DE')
return context
You can do something like:
urls.py
url(r'^(?P<country>germany|netherland)/$', FeedList.as_view(), name='feedlist')
and the view:
class FeedList(PaginationMixin, ListView):
model = FeedItem
context_object_name = 'feed_items'
match = {'germany':'DE','netherland':'NL'}
def get_queryset(self):
code = self.match[self.kwargs['country']]
items = FeedItem.objects.filter(feed__country__name=code)
self.template_name = '%s_feed.html' % code.lower()
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(FeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name=self.match[self.kwargs['country']])
return context
Also, perhaps you don't need two templates else only one, in this case just remove this line self.template_name = '%s_feed.html' % code.lower() and set the template_name accordingly.
Change your url to
url(r'^(?P<country>netherland|germany)/$', NLFeedList.as_view(), name='nl'),
Then access this new <country> parameter in your view using
country = self.kwargs['country']
Then do the necessary if country = '...': code block in your view.