Django - CreateView - How to declare variable and use it in templates - django

How do I declare a variable in Django's Createview, so I can use it from its template?
For example I want to use {{ place_slug }} in the template. I pass that from urls.py like below:
urls.py:
urlpatterns = patterns('',
(r'^new/(?P<place_slug>[\w\-\_]+)/?$', PictureCreateView.as_view(), {}, 'upload-new'),
)
views.py:
class PictureCreateView(CreateView):
model = Picture
def dispatch(self, *args, **kwargs):
self.place = get_object_or_404(Place, slug=kwargs['place_slug'])
return super(PictureCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
more code here

Override get_context_data and set context_data['place_slug'] = your_slug
Something like this:
def get_context_data(self, **kwargs):
context = super(PictureCreateView, self).get_context_data(**kwargs)
context['place_slug'] = self.place.slug
return context
Some more info on this in the Django docs.

in template you can use {{ title }}
class Something(generic.ListView):
template_name = 'app/example.html'
model = models.SomeModel
def get_context_data(self, **kwargs):
context = super(Something, self).get_context_data(**kwargs)
context["title"] = "Some title"
return context

Related

Transform function to class based view (recall same site)

Very similar to these questions I want to transform my view. The difference is that I want to return to the same page and I have problems adjusting my urls.py (I think):
So on the product_all.html I press a button and end up on the same page after the product was deleted:
def delete_product(request, pk):
Product.objects.filter(id=pk).delete()
context = {'Product': Product.objects.all()}
return render(request, 'gbkiosk/product_all.html', context)
urls.py:
path("product_delete/<int:pk>", views.delete_product, name='product-delete'),
I wanted to recreate that using a TemplateView:
class DeleteProduct(TemplateView):
template_name = "gbkiosk/device_all.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
Product.objects.filter(id=kwargs["product_id"]).delete()
context["products"] = Product.objects.all()
return context
but what would the corresponding urls.py entry be?:
path("product_delete/<int:product_id>", views.DeleteProduct.as_view(), name="product-delete")
This will not return me to product_all.html after clicking?
You should have a ListView to list the products and a DeleteView to delete the ones you like. That's cleaner.
So, it'd be something like:
views.py
class Products(ListView):
model = Product
class ProductDelete(DeleteView):
model = Product
success_url = reverse_lazy('product-list')
urls.py
from django.urls import path
from products.views import ProductListView
urlpatterns = [
path('', ProductListView.as_view(), name='product-list'),
path('<int:pk>', ProductDeleteView.as_view(), name='product-delete'
]
This is how I did it:
class ProductDeleteView(DeleteView):
model = Product
def get_success_url(self):
return reverse_lazy('product-list')
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ProductDeleteView, self).get_context_data(**kwargs)
print(kwargs)
return(context)
urls:
path("product_delete/<int:pk>", views.ProductDeleteView.as_view(), name='product-delete'),

when a database content deletes ,how can i redirect to same page using url argument passing method?

i want to delete a database content.bt after deletion utl goes to http://127.0.0.1:8004/login/delete_detail/6/ ..how can i redirect to success.html ie in the same page
class DeleteView(generic.TemplateView):
template_name = 'success.html'
success_url='/login/success'
def get_context_data(self, *args, **kwargs):
context = super(DeleteView,self).get_context_data(**kwargs)
did = self.kwargs['did']
q_obj = Quest.objects.filter(id=did)
q_obj.delete()
You should override get_success_url method. For example:
def get_success_url(self):
return reverse_lazy('delete-success')
Also, try to use named urls in your success_url
success_url = reverse_lazy('delete-success')
You can use get_success_url method:
from django.urls import reverse_lazy
class DeleteView(generic.TemplateView):
template_name = 'success.html'
success_url='/login/success'
def get_context_data(self, *args, **kwargs):
context = super(DeleteView,self).get_context_data(**kwargs)
did = self.kwargs['did']
q_obj = Quest.objects.filter(id=did)
q_obj.delete()
def get_success_url(self, **kwargs):
return reverse_lazy('delete_detail', kwargs = {'pk': self.kwargs['did']})
Also instead of TemplateView you can use DeleteView class:
class QuestDelete(DeleteView):
model = Quest
pk_url_kwarg = 'did'
def get_success_url(self, **kwargs):
return reverse_lazy('delete_detail', kwargs = {'pk': self.kwargs['did']})
To use url's name you need to add name argument to the url pattern inside urls.py file like this:
urlpatterns = [
path('delete_detail', views.delete_detail, name='delete_detail'),
]

How to inject same context in many different Django views?

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

django Initialising Choices in Form from Class Based View

I've seen a lot of answers about how this is solved when not using Class Based Views. Is there some really obvious thing I'm missing about doing it with CBVs?
Basically I want to have a MultipleChoiceField in my form which has choices determined by what is happening in the view. e.g. I use the PK from the URL to do some backend requests and then those should be used to populate the choices.
# forms.py
from django.forms import Form, MultipleChoiceField, CharField
class EmailForm(Form):
users = MultipleChoiceField(required=False)
subject = CharField(max_length=100)
message = CharField()
def __init__(self, users=None, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
if users:
self.fields['users'].choices = users
#urls.py
from django.conf.urls import url, patterns
from .views import EmailView
# url patterns
urlpatterns = patterns('',
url( r'^(?P<pk>\d+)$', EmailView.as_view(), name="maindex" ),
)
#views.py
from django.views.generic import FormView, TemplateView
from .forms import EmailForm
class EmailView(FormView):
template_name = 'myapp/email.html'
form_class = EmailForm
success_ulr = '/thanks/'
def form_valid(self, form):
# Do stuff here
return super(EmailView, self).form_valid(form)
Basically it boils down to how/where to call the init function from the view. How do I do that? Or is there another way I've missed? I thought of overriding get_form_kwargs in the view, but couldn't make that do anything.
Thanks
The view:
from django.views.generic import FormView
class EmailView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
# get users, note: you can access request using: self.request
kwargs['users'] = users
return kwargs
The form:
from django import forms import Form
class EmailForm(Form):
users = MultipleChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.users = kwargs.pop('users', None)
super(EmailForm, self).__init__(*args, **kwargs)
self.fields['users'].choices = self.users
Basically, what I've done in a similar case is the following (Python 3.5, Django 1.8):
def get_form(self, *args, **kwargs):
form= super().get_form(*args, **kwargs)
form.fields['rank'].choices= <sequence of 2-tuples>
return form
where obviously rank is the field name. This way I use the default form.
Alright, the FormMixin calls get_form to get the form-class which looks like
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return form_class(**self.get_form_kwargs())
So you can either override get_form to instance your Form yourself
def get_form(self, form_class):
return EmailForm(files=self.request.FILES or None,
data=self.request.POST or None,
users=some_user_queryset)
or stay a bit more generic and override get_form_kwargs to something like
def get_form_kwargs(self):
form_kws = super(EmailView, self).get_form_kwargs()
form_kws["users"] = some_user_queryset
return form_kws
One way to do it is:
class EmailView(FormView):
# ...
def get(self, request, *args, **kwargs):
self.users = ...
return super(EmailView, self).get(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
kwargs['users'] = self.users
return kwargs
This allows you to set the user choices in the view and to pass them to the form.
You can override get_form.
I needed to update the choices for ChoiceField based on logged in user.
Form:
class InteractionCreateForm(forms.Form):
device = forms.ChoiceField(choices=[(None, '----------')])
...
View:
class InteractionCreateView(FormView):
form_class = InteractionCreateForm
...
def get_form(self, form_class=None):
form_class = super().get_form(form_class=None)
form_class.fields['device'].choices = \
form_class.fields['device'].choices \
+ [(device.pk, device) for device in Device.objects.filter(owner=self.request.user.id)]
return form_class

Extending class, date based generic views in django and get_context_data?

I'm trying to extend a simple date based view (using the 1.3 generic class approach) with my own mixin "BaseViewMixin":
class BaseViewMixin(object):
"""define some basic context for our views"""
model = Alert
month_format = "%m"
date_field = "date"
def get_context_data(self, **kwargs):
"""extra context"""
context = super(BaseViewMixin, self).get_context_data(**kwargs)
context["CACHE_SERVERS"] = settings.CACHE_SERVERS
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(BaseViewMixin, self).dispatch(*args, **kwargs)
class IndexView(BaseViewMixin, TodayArchiveView):
template_name= "index.html"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
queryset = Alert.objects.default(self.day)
tickets = Alert.objects.tickets(self.day)
alert_groups = []
for item in tickets:
alert_groups.append({"ticket": item, "alerts": queryset.filter(ticket=item["ticket"])})
context["alert_groups"] = alert_groups
return context
The problem is that all of the date based things you normally get in the context are wiped out as soon as I override the get_context_data method for my IndexView class. Why is that? I would expect {{ day }}, {{ previous_day }} etc to show up in the context, as well as self. When I remove my get_context_data method all the generic date stuff works.
urls.py entry is just:
url(r'^$', IndexView.as_view(), name='index'),
This is one way to do it which allows me to reuse code. I'm still not understanding why get_context_data wipes out the TodayArchiveView context the way I define it in my original question. It seems that using the scenario below would do the same thing to my mixin but it doesn't. The ViewMixin context is preserved when calling get_context_data in DateMixin.
class ViewMixin(object):
"""define some basic context for our views"""
model = Alert
def get_context_data(self, **kwargs):
"""extra context"""
context = super(ViewMixin, self).get_context_data(**kwargs)
context["CACHE_SERVERS"] = settings.CACHE_SERVERS
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewMixin, self).dispatch(*args, **kwargs)
class DateMixin(object):
month_format = "%m"
date_field = 'date'
def get_alert_groups(self):
none, qs, dated_items = self.get_dated_items()
day = dated_items["day"]
queryset = Alert.objects.default(day)
tickets = Alert.objects.tickets(day)
alert_groups = []
for item in tickets:
alert_groups.append({"ticket": item, "alerts": queryset.filter(ticket=item["ticket"])})
return alert_groups
def get_context_data(self, **kwargs):
context = super(DateMixin, self).get_context_data(**kwargs)
context["alert_groups"] = self.get_alert_groups()
return context
class IndexView(ViewMixin, DateMixin, TodayArchiveView):
template_name= "index.html"
Not an exact answer to your question, but considering the fact that your goal seems to be adding a certain context to more than one view a context processor might be a good solution!
The problem arise because you're inhereting from 2 classes.
context = super(IndexView, self).get_context_data(**kwargs) will initialise the context with the method from BaseViewMixin not TodayArchiveView (super walks base classes from left to right) --> context variables from TodayArchiveView will be lost.
I think you can do this:
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context_day = super(BaseViewMixin, self).get_context_data(**kwargs)
context = dict(context, **context_day)
...