Combine CreateView with DetailView in Django 1.5 - django

In my app I need to create products in a shop. So I have a model Shop and a model Product. I can see details about my shop in a DetailView ShopDetail. Now I need a CreateView in order to create products, but the url should be /shops/shop-id/products/create/, so I create products inside the shop. I guess it's something like
class ProductCreate(SingleObjectMixin, CreateView):
model = Product
def get_object(self, queryset=None):
return Shop.objects.get(id = self.kwargs['shop_id'])
Am I on the right track? :-D

No, you're not on the right track: the object returned by get_object should be a instance of the model; in fact, if you override get_object the model attribute becomes irrelevant.
There are a few approaches to this problem, but I would myself probably got for a single DetailView (with the Shop details), and add a form for Product to the template via the get_context_data method. The form's action attribute would, instead of being empty, point to the url to a separate CreateView which would handle the Product creation.
Alternatively you could simply display the Shop details through the get_context_data, which is simpler but mixes concerns (as the DetailView for shop is defined as a CreateView for Product).

I think you need:
from django.shortcuts import get_object_or_404
class ProductCreate(CreateView):
"""Creates a Product for a Shop."""
model = Product
def form_valid(self, form):
"""Associate the Shop with the new Product before saving."""
form.instance.shop = self.shop
return super(CustomCreateView, self).form_valid(form)
def dispatch(self, *args, **kwargs):
"""Ensure the Shop exists before creating a new Product."""
self.shop = get_object_or_404(Shop, pk=kwargs['shop_id'])
return super(ProductCreate, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
"""Add current shop to the context, so we can show it on the page."""
context = super(ProductCreate, self).get_context_data(**kwargs)
context['shop'] = self.shop
return context
I hope it helps! :) You may wish to have a look at what the super-methods do.
(Disclaimer: Shameless self promotion.)

Related

Django/Python: How to write Create, List, Update and Delete in a single View or a generic view?

I am trying to write a view in which a post can be created and in the same page, the object_list will be displayed. And even an object can be updated and deleted.
Country Capital
India Delhi UPDATE DELETE
USA Washington UPDATE DELETE
----- ------
I would appreciate helping me in achieve this or suggesting a similar type of question.
What you're looking for are Mixins.
Try creating a detail view class with the following parameters:
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView
For example:
class ObjectDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Object.objects.all()
As has proposed by Daniel, if you like DRF, ViewSets are also a decent alternative. However, they're not exactly succinct so I generally avoid them when possible.
Something like a ModelViewSet, however, is extremely clear-cut and the approach I generally choose.
Here's an example:
class ObjectViewSet(viewsets.ModelViewSet):
queryset = Object.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Beautiful, isn't it?
For more details, see the DRF tutorial: http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
You are mixing view and template. View handle requests and template show content and links.
You will have ListView, which will contain list of posts. In template you add forms for update, form for create and forms for delete. Each form will have attribute action with link to proper view. So update forms will have link to url with UpdateView, create forms to CreateView, and delete to DeleteView. In each form you set redirect back to ListView. This way if you want to use only Django.
OR
If you really want to everything handle on one page without refreshing and redirecting. You can use ajax and django-rest-framework and its viewset. In viewset you can handle lists, create, update, push, detail, in one class.
Viewset:
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass

django admin site - display model objects created by request.user only

I have simple blog app which have author=models.ForeignKey(User, editable=False) field. Blog posts are created from django admin site, and I use save_model to get author which is request.user.
Now I want that user (is_staff) can see only his own posts, when he browse model posts. But by default all blog posts are displayed, so how can I hide blog posts created by other users? Of course superusers need to see all of them.
Override the get_queryset method on the ModelAdmin subclass. The documentation has an example of exactly what you're asking for: displaying only objects related to the current user.
In case anyone is lazy. Here's a sample code I use.
So first you create a QuerySet Manager, I usually have mine in models.py, which does something like this (in this case I'm calling the model Blog):
class BlogManager(models.Manager):
def get_queryset(self, request):
query = Blog.objects.filter(author=request.user)
if request.user.is_superuser:
query = UserProfile.objects.all()
return query
Make sure your admin.py then has this:
def get_queryset(self, request):
queryset = BlogManager.get_queryset(self, request)
return queryset
So it might look like:
class BlogAdmin(admin.ModelAdmin):
#add any other code here
#Using that queryset manager created before
def get_queryset(self, request):
queryset = BlogManager.get_queryset(self, request)
return queryset
admin.site.register(BlogAdmin)
Hope this helps anyone!

How to limit access to the UpdateView of an object to the creator of that object

Django and programming noob here. I've made an application I'd like to deploy, but I need to figure out how to limit access to the UpdateView to the creator of that object, and I'm stumped.
Currently a user can use the CreateView .../universities/create/ to create a university object, but then any user can use .../universities/update/ to edit that object. I want to configure this so only the user who is the creator (any user with the ManytoMany attribute 'administrator') of that university has access to the UpdateView for their university object.
Any advice would be appreciated. I've spent a few days on this and I haven't made much traction...thanks for reading.
models.py
class University(models.Model):
name = models.CharField(max_length=100)
about = models.TextField()
administrators = models.ManyToManyField(User)
profile_picture = models.FileField(upload_to=get_upload_file_name, blank=True)
def __unicode__(self):
return unicode(self.name)
def get_absolute_url(self):
return reverse('university_detail', kwargs={'pk': str(self.id)})
views.py
class UniversityCreateView(CreateView):
model = University
form_class = UniversityForm
template_name = 'university_create.html'
def form_valid(self, form):
f = form.save(commit=False)
f.save()
return super(UniversityCreateView, self).form_valid(form)
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
You can use UserPassesTestMixin as the documentation says:
limit access based on certain permissions or some other test
just implement test_func(self) that returns True if the user should enter the view.
You might write a code like this:
class UniversityUpdateView(UserPassesTestMixin,UpdateView):
def test_func(self):
return self.request.user.administrators_set.filter(pk=self.get_object().pk).exists()
model = University
form_class = UniversityForm
template_name='university_form.html'
youll have to include permission decorators on your views , further info is here https://docs.djangoproject.com/en/dev/topics/auth/ , & https://docs.djangoproject.com/en/dev/topics/auth/default/#topic-authorization
so if you want to limit your updateview to any user with the ManytoMany attribute 'administrator', youll have to do something like this:
views.py
from appname.users.decorators import requiresGroup
from django.contrib.auth.decorators import login_required
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
#method_decorator(requiresGroup("groupname" , login_url='/accounts/login/'))
def dispatch(self, request, *args, **kwargs):
return super(UniversityUpdateView, self).dispatch(request, *args, **kwargs)
also if you havent already youll have to include the following at the top of your models.py
from django.contrib.auth.modes import user
though Ill assume its there as youve defined your administrators with the user model
then go to the group seetings in the django admin ( should be a url like localhost/admin/auth/group , add your special adminstrator group name, then go to the admin user section (localhost/admin/auth/user), then make sure they have been put into the adminstrator group
then replace "groupname" in the #requiresGroup decorator with the actual name of the user group
the #requiresGroup decorator isnt a standard decorator, so it has to be written
make a folder path and file like appname/users.decorators.py
then in decorators.py write
from functools import update_wrapper , wraps
from django.utils.decorators import available_attrs
from django.http import HttpResponse, HttpResponseRedirect
def requiresGroup(groupname):
def decorator(view_function):
def _wrapped_view(request,*args,**kwargs):
if request.user.groups.filter(name=groupname).count()!=1:
return HttpResponseRedirect("/")
else:
return view_function(request,*args,**kwargs)
return wraps(view_function,assigned=available_attrs(view_function))(_wrapped_view)
return decorator
hope this helped
edit: made a mistake, put the decorators above the class, they should be in a function inside the class, noticed my mistake almost immediately so hopefully I havent caused any trouble
You can override the get method of your class based view (in this case UniversityUpdateView). Then in the method check if user has rights to access the page and if not raise exception or redirect the user to another page. If the user has enough rights to access the page then just let the normal behavior go on.
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
def get(self, request, *args, **kwargs):
if request.user.groups.filter(name=groupname).count()!=1:
return HttpResponseRedirect("/")
return super().get(request, *args, **kwargs)

How to clean form data depending on logged-in user in django admin panel?

I've looked at several questions here that looked similar, but none of them discussed the problem from the perspective of admin panel.
I need to check if user has permission to leave a field empty. I wanted to use request.user but I don't knot how to pass request from EntryAdmin to ModelForm. I wanted to do something like this:
class EntryAdminForm(ModelForm):
class Meta:
model = Entry
def clean_category(self):
if not self.request.user.has_perm('blog.can_leave_empty_category') and not bool(self.category):
raise ValidationError(u'You need to choose a Category!')
else:
return self.cleaned_data['category']
You could override the ModelAdmin.get_form, by adding the request as an attribute of the newly created form class (should be thread-safe).
Something along these lines:
class EntryAdmin(admin.ModelAdmin):
form = EntryAdminForm
def get_form(self, request, *args, **kwargs):
form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
Then the code in your question should work.

How to filter queryset in changelist_view in django admin?

Let's say I have a site where Users can add Entries through admin panel. Each User has his own Category he is responsible for (each Category has an Editor assigned through ForeingKey/ManyToManyField).
When User adds Entry, I limit the choices by using EntryAdmin like this:
class EntryAdmin(admin.ModelAdmin):
(...)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'category':
if request.user.is_superuser:
kwargs['queryset'] = Category.objects.all()
else:
kwargs['queryset'] = Category.objects.filter(editors=request.user)
return db_field.formfield(**kwargs)
return super(EntryAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This way I can limit the categories to which a User can add Entry and it works perfect.
Now the tricky part: On the Entry changelist/action page I want to show only those Entries which belong to current User's Category. I tried to do this using this method:
def changelist_view(self, request, extra_context=None):
if not request.user.is_superuser:
self.queryset = self.queryset.filter(editors=request.user)
But I get this error:
AttributeError: 'function' object has no attribute 'filter'
This is strange, because I thought it should be a typical QuerySet. Basically such methods are not well documented and digging through tons of Django code is not my favourite sport.
Any ideas how can I achieve my goal?
Warning: This answer is from 2010, and is not useful for Django >= 1.8.
queryset is a method on ModelAdmin which returns a queryset. You need to override it on your EntryAdmin class.
def queryset(self, request):
qs = super(EntryAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(editors=request.user)
Changing the queryset will limit the Entries shown in the list view. You also need to override has_change_permission to ensure that the user has permission to edit the object on the individual object editing page. See the following blog post by James Bennett for further details:
http://www.b-list.org/weblog/2008/dec/24/admin/