I am trying to implement a template for adding VIEW, ADD and EDIT webpages with pluggable views. How do I use a url value like ?
This is the code I am trying to translate into pluggable views.
#app.route('/edit/category/<category>', methods=['GET', 'POST'])
def editCategory(category):
form = forms.AddCategory()
form.name.data = category
if form.validate_on_submit():
newName = form.name.data
database.editCategory(name = category, newName = newName)
#view single category?
return redirect('/view/categories/')
return render_template('edit-category.html', category = category, form = form)
Pluggable View Code
class ListView(View):
def getTemplate_name(self):
raise NotImplementedError()
def render_template(self, context):
return render_template(self.get_template_name(), **context)
def dispatch_request(self):
context = self.get_context()
return self.render_template(context)
class CategoryView(ListView):
def get_template_name(self):
return 'categories.html'
def get_objects(self):
return models.Category.query.all()
def get_form(self):
return forms.AddCategory()
def get_context(self):
return {'categories': self.get_objects(), 'form': self.get_form()}
app.add_url_rule('/view/categories', view_func=CategoryView.as_view('category'))
class EditCategory(ListView):
def get_template_name(self):
return 'edit-category.html'
def get_form(self, category):
form = forms.AddCategory()
form.name.data = category
return form
def get_context(self):
return {'form': self.get_form()}
app.add_url_rule('/edit/category/<category>', view_func=EditCategory.as_view('category'))
You need to override the dispatch_request method in the EditCategory class as this method has the url values passed to it.
class EditCategory(View):
...
def get_context(self, category):
return {'form': self.get_form(category)}
def dispatch_request(self, category):
context = self.get_context(category)
return self.render_template(context)
app.add_url_rule('/edit/category/<category>', view_func=EditCategory.as_view('category'))
As an aside, I think you are better off with the original decorated view functions in this case is there is very little commonality between the viewCategory and editCategory functionality
Related
i have a view method such as
#login_required(login_url='/users/login')
def my_submission(request,submission_id):
submission = Submission.objects.get(pk=submission_id)
return render(request, "assignments/mysubmission.html", {
"submission": submission
})
I was wondering if there is a way to pass submission_id which is the second param to user passes test decorator so that i can do something like
#user_passes_test(lambda u: u.id == Submission.objects.get(pk=submission_id).user.id, login_url='/')
thanks for the guide in advance.
You should just write it as a check in the view:
#login_required(login_url='/users/login')
def my_submission(request,submission_id):
submission = Submission.objects.get(pk=submission_id)
if submission.user_id != request.user.pk:
return redirect('/')
return render(request, "assignments/mysubmission.html", {
"submission": submission
})
Update
If you use this many times, consider implementing it is a class-based view, so you could inherit and utilize some extra features:
class UserIsOwnerMixin(AccessMixin):
"""Verify that the user the owner of related object."""
owner_id_field = 'user_id'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or getattr(self.get_object(), self.owner_field) != request.user.pk:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class SubmissionView(UserIsOwnerMixin, DetailView):
template = "assignments/mysubmission.html"
model = Submission
context_object_name = "submission"
login_url = '/users/login'
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 have to specify a success_url, otherwise I get an error. So how to specify it, in order to stay to the same page?
Also, is everything else correct regarding the SearchView, beucase I have a feeling that something is missing. My context should be composed by form, query, concepts, language and languages.
Thanks
urls.py
url(r'^(?P<langcode>[a-zA-Z-]+)/search/$', SearchView.as_view(), name='search').
views.py
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
class SearchView(_LanguageMixin, FormView):
template_name = "search.html"
form_class = SearchForm
success_url = #......
query = ''
concepts = []
def get_initial(self):
return {'langcode': self.langcode}
def get_context_data(self, **kwargs):
context = super(SearchView, self).get_context_data(**kwargs)
context.update({"query": self.query, "concepts": self.concepts})
return context
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return super(SearchView, self).form_valid(form)
[EDIT]
I did this:
def get_success_url(self):
return reverse('search', kwargs={'langcode': self.langcode})+"?query={}".format(self.query)
The form renders, but whenever I search for anything, I get back the empty search text field. And the URL looks something like this: http://localhost:8000/en-US/search/?query=asd
By default, a FormView (actually, any subclass of ProcessFormView) will return a HttpResponseRedirect in form_valid. As you are calling the super's method in your form_valid method, you also return a HttpResponseRedirect. In the process, the actual POST data is lost, and though you pass it as a GET parameter, it is not used in the actual form.
To fix this, you need to not call super in your form_valid method, but instead return a rendered template in a HttpResponse object, e.g.:
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return self.render_to_response(self.get_context_data(form=form))
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)