django use Pk field in a url - django

I have created a pk field(not automatically created by the model).
when I want to use this pk name(in my case school_id) I got this error:
Generic detail view SchoolDetailView must be called with either an object pk or a slug.
although I am using the correct pk name(school_pk) I have used in my model in the url.
my code is as follows:
models.py:
from django.db import models
from django.urls import reverse
class School(models.Model):
school_pk = models.AutoField(primary_key=True)
name = models.CharField(max_length=256)
principal = models.CharField(max_length=256)
location = models.CharField(max_length=256)
def __str__(self):
return str(self.name)
def get_absolute_url(self):
return reverse("basic_app:school_detail",kwargs={'school_pk':self.school_pk})
views.py:
class SchoolListView(ListView):
model = models.School
template_name = 'basic_app/school_list.html'
class SchoolDetailView(DetailView):
context_object_name = 'school'
model = models.School
template_name = 'basic_app/school_detail.html'
urls.py:
urlpatterns = [
path('',views.SchoolListView.as_view(),name='school_list'),
path('<int:school_pk>/',views.SchoolDetailView.as_view(),name='school_detail'),]
when I try to go to school_detail page by going to http://127.0.0.1:8000/basic_app/1/ for example
I have this error:
Request Method: GET
Request URL: http://127.0.0.1:8000/basic_app/1/
Django Version: 2.0
Exception Type: AttributeError
Exception Value:
Generic detail view SchoolDetailView must be called with either an object pk or a slug.
I have tried to use get_object and/or get_queryset but I believe I am doing it the wrong way. if any one can help that will be great and highly appreciated. Thanks.
note:I dont want to omit school_pk field from my model and use PK that is automatically genertaed).

Add pk_url_kwarg = 'school_pk' to view SchoolDetailView. By default it is set to pk.
class SchoolDetailView(DetailView):
context_object_name = 'school'
model = models.School
template_name = 'basic_app/school_detail.html'
pk_url_kwarg = 'school_pk'

Related

How to use Django function based view to update a model?

I used a class based view to update a user profile using this code
class EditProfileViewClass(generic.UpdateView):
model = UserProfile
fields = ['bio', 'profile pic']
template_name = 'users/update.html'
success_url = reverse_lazy('home')
path('profile/<int:pk>/update', EditProfileViewClass.as_view(), name="profile"),
Your Profile
the issue right now is, Instead of having the url like the one above, I want it to be like
path('profile/<str:username>/update', EditProfileViewClass.as_view(), name="profile"),
but unfortunately I get an attribute error saying:
Generic detail view EditProfileView must be called with either an object pk or a slug in the URLconf.
So I tried making a function based view so I can get the "username" from the url, doing that didn't allow me to get the form I needed to update the specific username.
Any help would be great. Thanks.
In your EditProfileViewClass view you can add pk_url_kwarg or slug_url_kwarg.
class EditProfileViewClass(UpdateView):
model = UserProfile
fields = ['bio', 'profile pic']
template_name = 'users/update.html'
success_url = reverse_lazy('home')
pk_url_kwarg = 'username'
slug_url_kwarg = 'username'
You can use a class-based view, you only need to update the the slug_field (which determines on what should be used), and the slug_url_kwargs:
class EditProfileViewClass(UpdateView):
model = UserProfile
fields = ['bio', 'profile_pic']
template_name = 'users/update.html'
success_url = reverse_lazy('home')
slug_field = 'user__username'
slug_url_kwarg = 'username'
This will thus take the username parameter of the URL, and it will filter the queryset to only retrieve a UserProfile that is linked to a user with that username.

MultipleObjectsReturned error in Django but I want multiple objects to be returned

Using Django REST framework I created an url which maps to a page with a JSON file containing all the objects in my database.
I want to do the same but instead of showing all the objects I want only the objects that match a specific category (category is an attribute in my model).
I have urls that show a JSON files with a single object in it (using the pk attribute) but when I try to do the same thing with category instead of pk I get a MultipleObjectsReturned error.
I'm just sperimenting with the REST framework, I tried using different views and class based views solving nothing.
Any hint or suggestion is really appreciated thanks.
# models.py
class Hardware(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=2)
def get_api_url(self):
return api_reverse("category-api-postings:post-rud", kwargs={'category': self.category})
#views.py
class HardwareListView(generics.ListCreateAPIView):
pass
lookup_field = 'pk'
serializer_class = HardwareSerializer
def get_queryset(self):
query = self.request.GET.get("q")
qs = Hardware.objects.all()
if query is not None:
qs = qs.filter(Q(title__icontains=query) | Q(content__icontains=query)).distinct()
return qs
class HardwareRudView(generics.RetrieveUpdateDestroyAPIView):
pass
lookup_field = 'category'
serializer_class = HardwareSerializer
def get_queryset(self):
return Hardware.objects.all()
#urls.py
app_name = 'category-api-postings'
urlpatterns = [
path('', exercise_view),
path('list-api/', HardwareListView.as_view(), name='all'),
path('list-api/<str:category>/', HardwareRudView.as_view(), name='post-rud')
#serializer.py
class HardwareSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Hardware
fields = [
'url',
'pk',
'name',
'category'
]
read_only_fields = ['user']
def get_url(self, obj):
return obj.get_api_url()
As I understand, you want url /list-api/HD/ to return all Hardware objects from given category. For that HardwareRudView must inherit ListAPIView, not RetrieveUpdateDestroyAPIView. For example, like this:
class HardwareRudView(generics.ListAPIView):
serializer_class = HardwareSerializer
def get_queryset(self):
category = self.kwargs['category']
return Hardware.objects.filter(category=category)
See related docs: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-url

How to achieve nested URL for OneToOne Relationship?

I'm a little bit stuck with the following situation. I want to build a REST API for a shopping cart app using the Django Rest Framework, however, due to legacy requirements I need to work with nested URLs.
In general, I have two resources AppUsers and Carts. Both of the resources are available at the default /appUsers/ and /carts/ endpoints. I then tried using nested routers to get the cart detail view for a specific user to be addressable as /appUsers/app_user_pk/cart/ instead of /carts/pk/ since every AppUser can only have one cart anyway.
Here's my setup:
models.py
class AppUser(models.Model):
_id = models.AutoField(primary_key=True)
class Meta:
default_related_name = 'app_users'
class Cart(models.Model):
app_user = models.OneToOneField(
'AppUser',
on_delete=models.CASCADE,
related_query_name='cart',
)
class Meta:
default_related_name = 'carts'
def __str__(self):
return "{user} cart".format(user=self.app_user._id)
serializers.py
class AppUserSerializer(serializers.HyperlinkedModelSerializer):
cart = serializers.HyperlinkedIdentityField(view_name='cart-detail')
class Meta:
model = AppUser
fields = '__all__'
class CartSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Cart
fields = '__all__'
views.py
class AppUserViewSet(viewsets.ModelViewSet):
"""
Model viewset for AppUser model
"""
queryset = AppUser.objects.all()
serializer_class = AppUserSerializer
class CartViewSet(viewsets.ModelViewSet):
"""
List all carts, or create new / edit existing product.
"""
queryset = Cart.objects.all()
serializer_class = CartSerializer
def get_queryset(self):
if 'app_user_pk' in self.kwargs:
return Cart.objects.filter(app_user=self.kwargs['app_user_pk'])
return Cart.objects.all()
urls.py
router = DefaultRouter()
router.register(r'appUsers', views.AppUserViewSet)
router.register(r'carts', views.CartViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^appUsers/(?P<app_user_pk>[0-9]+)/cart/$', views.CartViewSet.as_view({'get': 'retrieve'}), name='cart-detail'),
]
However, I can't get it to work, whenever I try something new I run into a different issue. So I was wondering what the best way is to achieve such task?
Essentially I just want to have cart-detail view for the one shopping cart the an AppUser can have under /appUsers/app_user_pk/cart/
SOLUTION:
I used the accepted answer to deal with the issue above. Additionally I had another ModelViewSet located at /appUsers/app_user_pk/cart/products, which I then registered using a NestedDefaultRouter from drf-nested-routers at cart/products like this:
cart_products_router = routers.NestedDefaultRouter(router, r'appUsers', lookup='app_user')
cart_products_router.register(r'cart/products', views.CartProductViewSet, base_name='cartproduct')
You can create a custom method to AppUserViewSet:
class AppUserViewSet(viewsets.ModelViewSet):
"""
Model viewset for AppUser model
"""
queryset = AppUser.objects.all()
serializer_class = AppUserSerializer
#action(detail=True)
def cart(self, request, pk):
obj = Cart.objects.get(app_user_id=pk)
serializer = CartSerializer(obj)
return Response(serializer.data)

UpdateView without a PK or Slug

Having trouble with an UpdateView. I've tried over writing the get_object but I am getting
AttributeError at /companydata/update/
'User' object has no attribute 'get_companydata'
The CompanyData Model has a OneToOne relationship with User.
Here's my code:
urls.py
### Omitted ###
url(r'^update/$', CompanyDataUpdateView.as_view(),
name='companydataupdate')
### Omitted ###
views.py
class CompanyDataUpdateView(UpdateView):
model = CompanyData
fields = ['arr', 'num_cust']
template_name = 'company_data/companydata_form.html'
def get_object(self):
return self.request.user.get_companydata()
models.py
class CompanyData(models.Model):
user = models.OneToOneField(User)
arr = models.DecimalField(max_digits=20, decimal_places=2, validators=[MinValueValidator(1)])
num_cust = models.IntegerField(validators=[MinValueValidator(1)])
def get_absolute_url(self):
return reverse('companyrevenue')
Any help would be greatly apprecaited!
The User object has no method called get_companydata, hence your error. You need to access the reverse one-to-one relationship like so:
def get_object(self):
return self.request.user.companydata
Where companydata is a property, not a method (i.e., don't call it with brackets). This is the default reverse name for the one-to-one relationship:
If you do not specify the related_name argument for the OneToOneField, Django will use the lower-case name of the current model as default value.
If you want to be more explicit or use another name, then set the related_name on your OneToOneField.

Trouble using "Slug" in Django DetailView

models.py
class Tag(models.Model):
name = models.CharField(max_length=64, unique=True)
slug = models.SlugField(max_length=255, unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Tag, self).save(*args, **kwargs)
urls.py
url(r'^tag/(?P<slug>[A-Za-z0-9_\-]+)/$', TagDetailView.as_view(), name='tag_detail'),
views.py
class TagDetailView(DetailView):
template_name = 'tag_detail_page.html'
context_object_name = 'tag'
Well, I thought this would work without any problem, because Django's generic DetailView will look for "slug" or "pk" to fetch its object. However, navigating to "localhost/tag/RandomTag" gives me an error:
error:
ImproperlyConfigured at /tag/RandomTag/
TagDetailView is missing a queryset. Define TagDetailView.model, TagDetailView.queryset, or override TagDetailView.get_queryset().
Does anyone know why this is happening...???
Thanks!!!
because Django's generic DetailView will look for "slug" or "pk" to fetch its object
It will, but you haven't told it what model it is to use. The error is very clear about this:
Define TagDetailView.model, TagDetailView.queryset, or override TagDetailView.get_queryset().
You can use the model or queryset attributes to do this, or the get_queryset() method:
class TagDetailView(...):
# The model that this view will display data for.Specifying model = Foo
# is effectively the same as specifying queryset = Foo.objects.all().
model = Tag
# A QuerySet that represents the objects. If provided,
# the value of queryset supersedes the value provided for model.
queryset = Tag.objects.all()
# Returns the queryset that will be used to retrieve the object that this
# view will display. By default, get_queryset() returns the value of the
# queryset attribute if it is set, otherwise it constructs a QuerySet by
# calling the all() method on the model attribute’s default manager.
def get_queryset():
....
There are a few different ways of telling the view where you want it to grab your object from, so have a read of the docs for more