I am trying to set up hyperlinking in my Django REST Framework API, and for the life of me I can't find out where my error is.
My model:
class Franchise(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
# Other fields
My serializer
class FranchiseListSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='franchise_details',
lookup_field='id',
lookup_url_kwarg='franchiseid'
)
class Meta:
model = Franchise
fields = ('id', 'name', 'url')
My URLs:
url(r'^db/franchise/$', views.franchise_index, name='db_franchise_index'),
url(r'^db/franchise/(?P<franchiseid>[0-9]+)/$', views.franchise_details, name='db_franchise_details')
Note that this is an included url conf, all my api functionality goes within an /api/ url
My views:
#api_view(['GET'])
def franchise_index(request, format=None):
franchise_list = Franchise.objects.all()
serializer = FranchiseListSerializer(franchise_list, many=True, context={'request': request})
return Response(serializer.data)
#api_view(['GET'])
def franchise_details(request, franchiseid, format=None):
franchise = Franchise.objects.get(id=franchiseid)
serializer = FranshiseDetailSerializer(franchise)
return Response(serializer.data)
Note that FranshiseDetailSerializer seen above works just fine.
Summary:
URL /api/db/franchise/ goes to the view franchise_index, which returns data serialized by FranchiseListSerializer.
URL /api/db/franchise/<franchiseid>/ goes to the view franchise_details, which returns data serialized by FranchiseDetailSerializer (Works fine)
As you can see, I have added a url field to FranchiseListSerializer, which I supposed to link to the corresponding franchise details page. Before I added the url field, the serializer only returned id and name, which was the correct and expected behaviour at the time.
When I go to /api/db/franchise/ now, I get the error:
ImproperlyConfigured at /api/db/franchise/
Could not resolve URL for hyperlinked relationship using view name "franchise_details". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
Following this hint, I set up the arguments in the url field of FranchiseListSerializer, and as far as I can tell, they are correct. I have checked and double checked the DRF documentation, here, here and here, but have found no solution.
Following other similar issues on Stackoverflow, I tried changing view_name='franchise_details' to view_name='api:franchise_details' (the name of the Django app the relevant files are in) and view_name='api:franchise_details-detail', but to no avail.
Any and all help is appreciated, cheers.
Thanks to #AKS's promting, I figured it out. view_name is actually the name of the URL, not the view. From the way I read the documentation (and the fact that it is view_name not url_name), it seemed to say that it was supposed to be the name of the view.
I had actually tried using view_name='db_franchise_details' (my urls name) before, but that did not work. After AKS promted my with that comment, I tried again, and also tried using view_name='api:db_franchise_details', which does work!
Related
So i'm working on an inventory app, a django-app where you can handle inventory in a company. I've been using mostly class-based so far in my views.py, and i can already create and edit all of my models using class-based views.
So when it comes to the generic.DeleteView, i have some problems.
This is my function in views.py:
class DeleteItemView(DeleteView):
model: Item
success_url: reverse_lazy('inventory_app:items')
template_name = 'inventory/detail_pages/item_detail.html'
And this is my URL to the function:
path('items/<int:pk>/delete/', views.DeleteItemView.as_view(), name='delete_item')
When i call this url with a button, this error appears:
DeleteItemView is missing a QuerySet. Define DeleteItemView.model, DeleteItemView.queryset, or override DeleteItemView.get_queryset().
So i heard online that this appears when /<int:pk>/ is missing in the url. But i have it in mine, so whats the problem here?
Thank you already
class DeleteItemView(DeleteView):
model = Item
success_url = reverse_lazy('inventory_app:items')
template_name = 'inventory/detail_pages/item_detail.html'
remove the colon (:) and change to =
I am making a simple webapp with Django. A user can have a profile, and under that profile create a blog post.
For example:
"path('profile/<int:pk>/',profile, name='profile')"
Returns the URL
"http://127.0.0.1:8000/profile/1/"
A user can then write blog posts which have the name in the URL
Example:
path('profile/<int:pk>/blog/<str:name>',Blogs, name='Blogs'),
Returns the URL
"http://127.0.0.1:8000/profile/1/blog/HelloWOrld"
However, IF two different users both name their blogs the same exact name, i get a 'MultipleObjectsReturned' Error.
I thought that by having the user PK earlier in the URL it would ensure that it would be unique, even if two blogs were called the exact same thing.
Views.py
def Blog(request, pk, name):
blog = Restaurant.objects.get(name=name)
user = CustomUser.objects.get(pk=pk)
if not user.id == request.user.pk:
raise PermissionDenied()
else:
context = {
'user': user,
'blog': blog,
}
return render(request, 'blog/blogs.html',context)
IS there any way to work around this without using the PK of the blog as well?
And if anyone could explain why my logic was wrong and it wasn't working in the first place.
Thanks.
You need to make sure you get the blog of that name of that user. I don't know exactly how your blog models look, but it's going to be something like
user = CustomUser.objects.get(pk=pk)
blog = Restaurant.objects.get(name=name, user=user)
And on the model, use the 'unique_together' property to ensure that the combination of user and blog name are unique, otherwise these URLs aren't going to work. Having the name completely unique as in George's answer isn't necessary and would mean that users couldn't create blog posts with titles already made by another user.
You need to make name field unique, and use SlugField for this if you want to use clean url:
class Restaurant(models.Model):
name = models.CharField(unique=True, ...)
slug = models.SlugField(unique=True, ...)
...
Hard facts:
I am using Django 2.0 with python 3.6, if it makes any difference.
What I am trying to achieve is a link to a list of objects that belong to a summary.
I have a ManyToOne relationship in my models.py.
class Summary(models.model):
type=models.CharField
class Object(models.Model):
summary= models.ForeignKey(Summary, on_delete=models.CASCADE)
in urls.py
object_list= views.ObjectListViewSet.as_view({
'get': 'list'
})
urlpatterns = format_suffix_patterns([
url(r'^summary/(?P<pk>[^/.]+)/objects/$', object_list, name='summary-objects')
])
and now the idea was to give a user the possibility to click the an url in the browsable API and getting all objects.
So, I tried to write a MethodField in serializers.py. I am not able to get any reasonable URL here, the only solution would be to hardcode it.
class SummarySerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="app:summary-detail")
objects= serializers.SerializerMethodField('get_obj_url')
def get_obj_url(self, obj):
pass
class Meta:
model = Summary
Is this possible?
Is it necessary to write a MethodField?
If yes, how do I get the url I need?
Actually, reverse, as suggested in the comments, does the trick.
The solution is:
def get_obj_url(self, obj):
request = self.context.get('request')
return request.build_absolute_uri(reverse('api-root')) + 'summary/{id}/objects'.format(
id=obj.id)
EDIT:Typo
I want to practice testing on Django, and I have a CreateView I want to test. The view allows me to create a new post and I want to check if it can find posts without a publication date, but first I'm testing posts with published date just to get used to syntax. This is what I have:
import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
from .models import Post, Comment
# Create your tests here.
class PostListViewTest(TestCase):
def test_published_post(self):
post = self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
response = self.client.get(reverse('blog:post_detail'))
self.assertContains(response, "really important")
But I get this:
django.urls.exceptions.NoReverseMatch: Reverse for 'post_detail' with no
arguments not found. 1 pattern(s) tried: ['post/(?P<pk>\\d+)/$']
How do I get the pk for that newly created post?
Thank you!
You can get it directly from the database.
Note, you shouldn't call two views in your test. Each test should only call the code it is actually testing, so this should be two separate views: one to call the create view and assert that the entry is in the db, and one that creates an entry directly and then calls the detail view to check that it displays. So:
def test_published_post(self):
self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
self.assertEqual(Post.objects.last().title, "Super Important Test")
def test_display_post(self):
post = Post.objects.create(...whatever...)
response = self.client.get(reverse('blog:post_detail', pk=post.pk))
self.assertContains(response, "really important")
What you wanna do with testing is using the relyable Django Databse API for recevieng the created data and see if your view represents this data.
As you only create 1 model instance and save it. You may obtain its pk via
model_pk = Post.objects.get(author="manualvarado22").pk
This pk then should be inserted into your url as the Exception states.
But i also recommend abseconded test, where you directly check if the newly created "Post" exists in the DB via django model API.
Edit:
When testing, django or the test Module you are using, creates a clean database only for testing and destroys it after the test-run. So if you want to acces a User while testing you must have created the user in your Test setup or in the Test method itself. Otherwise the Usertable would be empty.
I was finally able to solve the issue thanks to your great answers as well as some extra SO research. This is how the test looks like:
def test_display_no_published_post(self):
test_user = User.objects.create(username="newuser", password="securetestpassword")
post = Post.objects.create(author=test_user, title="Super Important Test", content="This is really important.")
response = self.client.get(reverse('blog:post_detail', kwargs={'pk':post.pk}))
self.assertEqual(response.status_code, 404)
And this are the create and detail views:
class PostDetailView(DetailView):
model = Post
def get_queryset(self):
return Post.objects.filter(published_date__lte=timezone.now())
class PostCreateView(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'blog/post_detail.html'
form_class = PostForm
model = Post
I am new to django. I made a form. I want that if the form is filled successfully then django should redirect to a success page showing the name entered in the form but no parameters should be present in the url itself.
I searched on the internet and the solution I got was to redirect to url with pk as a get parameter which fetches the data and shows in the view. But I don't want to pass any thing in the url itself. and some websites say that http can't redirect with post data.
Here's my views.py
class UserRegistrationView(CreateView):
model = UserForm
template_name = 'userregistration.html'
form_class = UserForm
success_url = 'success'
def get_success_url(self):
return reverse('success',kwargs = {'name' : self.object.firstName})
and here's the template to which I want to redirect:
<h2>Congratualations for registering {{name}} </h2>
Basically what I want is that if the person fill form mentioning his/her firstName as "xyz" then the redirected success page should say that "Congratulations for registering xyz"
You can use django sessions, which I believe installed by default in 1.8
Look here
# Set a session value:
request.session["fav_color"] = "blue"
# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]
# Clear an item from the session:
del request.session["fav_color"]
You can pass your pk via session and extract your object in another view without affecting your url.
Make sure you clean up after yourself.
Let me know if more help needed.
One of the possible ways of passing data between views is via sessions. So, in your UserRegistrationView you need to override the form_valid method as shown below.
class UserRegsitrationView(CreateView):
def form_valid(self,form):
self.request.session['name'] = self.object.firstName
return super(UserRegistrationView,self).form_valid(form)
class SuccessView(TemplateView):
template_name = "success_template.html"
def get_context_data(self,**kwargs):
context = super(SuccessView,self).get_context_data(**kwargs)
context['name'] = self.request.session.get('name')
del self.request.session['name']
return context
One more thing that you can modify in your code is that you need not declare success_url if you are overriding get_success_url