Django 2.0 UpdateView NoReverseMatch - python

PLEASE IGNORE!
Trying to use UpdateView to update a model object with name "Tag"
Here is the model:
NAME_REGEX = r'^[a-zA-Z0-9\s.\-]+$'
class Tag(TimeStampModel):
name = models.CharField(max_length=40,
validators = [
RegexValidator(regex=NAME_REGEX,
message='name must be alphanumeric, may contain - _',
code='invalid tag name')],
unique=True)
slug = models.SlugField(max_length=60, unique=True, blank=True)
text = models.TextField(blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
url = reverse('tags:tag_detail', kwargs={'slug':self.slug})
print('Tag *************** 010 ********************')
print(url)
return url
def save(self, *args, **kwargs):
self.slug = make_slug(self.name)
super().save()
I am able to create a Tag using CreateView, list the Tags using ListView and access the Tag detail using DetailView. I am also able to edit the tag using a function based view. However, it falls down with UpdateView.
Here are the urls:
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('tag_list/', views.TagListView.as_view(), name='tag_list'),
path('tag_detail/<slug:slug>/', views.TagDetailView.as_view(),
name='tag_detail'),
path('create_tag/', views.CreateTagView.as_view(),
name = 'create_tag'),
path('update_tag/<slug:slug>/', views.UpdateTagView.as_view(),
name = 'update_tag'),
path('category_list/', views.CategoryListView.as_view(),
name = 'category_list'),
path('category_detail/<slug>/', views.CategoryDetailView.as_view(),
name = 'category_detail'),
]
In a template called "tag_detail.html" I provide a link that should take the user to an edit form as follows:
<p class="slm-red">
{{ tag_dict.slug }}
<br>
Edit Tag
</p>
(I've printed the value of the slug before the link. That is not the problem. The slug value is correct. in this case slug = "brown")
When I click the link it takes me to:
http://localhost:8000/tags/update_tag/brown/
In this case the slug has the value "brown"
I get the following error message:
Reverse for 'update_tag' with no arguments not found. 1 pattern(s) tried: ['tags\\/update_tag\\/(?P<slug>[-a-zA-Z0-9_]+)\\/$']
I tried it the old fashioned way using "url" instead of path and a regular expression for the slug with the same result.
It all works fine when I use a function based view passing slug as a parameter.
Here is the UpdateTagView:
class UpdateTagView(UpdateView):
print('UpdateTagView ************** 010 *****************')
template_name = 'tags/update_tag.html'
model = Tag
fields = ['text']
The first line is a print statement. It does not appear on the console log so this confirms that Django never gets as far as the actual view. What I do get on the console log includes the following:
[22/Jan/2018 16:06:00] "GET /tags/tag_detail/brown/ HTTP/1.1" 200 4852
Internal Server Error: /tags/update_tag/brown/
I've tried several variants but nothing works. I am getting quite desperate.
I've seen similar questions on stack overflow for previous versions of Django but there never seems to be an answer.
When I use an FBV it's simple. I just pass the slug as a parameter along with request.
For what it's worth the errors seems to be occurring in base.py. At the end of the error messages I see:
C:\django\_dj\projenv\lib\site-packages\django\urls\base.py in reverse
I think there's a kwarg I'm supposed to override but I have no notion how to do it. Since Django never gets as far as the UpdateTagView nothing I do there can affect the outcome.

Related

TypeError & AssertionError when clicking on buttons in Django

Apologies for the long post, I am trying to implement a simple button which either add or remove an item from a watchlist.
While I initially managed to implement the "addwatchlist" function appropriately, I tinkered my code for a few hours and I somewhat am unable to wrap my head around it again.
Here is the error that I receive when pressing the "Add to Watchlist" button :
TypeError at /addwatchlist/10
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Request Method: GET
Request URL: http://127.0.0.1:8000/addwatchlist/10
Django Version: 3.1.1
Exception Type: TypeError
Exception Value:
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
TRACEBACK :
watchlist.save()
▼ Local vars
Variable Value
id 10
request <WSGIRequest: GET '/addwatchlist/10'>
watchlist Error in formatting: TypeError: Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Here is the error that I receive when pressing the "Remove from Watchlist" button, note that this is the exact same error I initially received which in turn forced me to try and tweak the way "Add to Watchlist" function :
AssertionError at /removewatchlist/1
Watchlist object can't be deleted because its id attribute is set to None.
Request Method: GET
Request URL: http://127.0.0.1:8000/removewatchlist/1
Django Version: 3.1.1
Exception Type: AssertionError
Exception Value:
Watchlist object can't be deleted because its id attribute is set to None.
TRACEBACK :
watchlist.delete()
▼ Local vars
Variable Value
id 1
request <WSGIRequest: GET '/removewatchlist/1'>
watchlist <Watchlist: 1 Watchlist ID# None : admin watchlisted : "Iphone 11 Pro">
Models.py :
class Listing(models.Model):
owner = models.CharField(max_length=64)
title = models.CharField(max_length=64)
description = models.TextField(max_length=1024)
startingBid = models.IntegerField()
link = models.URLField(blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_id")
time = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(null=False, default='True')
def __str__(self):
return f'"{self.title}"'
class Watchlist(models.Model):
user = models.CharField(max_length=64)
watchlist_listingid = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="watchlist_listingid", null=True)
def __str__(self):
return f'{self.watchlist_listingid_id} Watchlist ID# {self.id} : {self.user} watchlisted : {self.watchlist_listingid}'
Views.py :
#login_required
def watchlist(request):
if request.user.username:
return render(request, "auctions/watchlist.html", {
"items" : Watchlist.objects.filter(user=request.user.username),
"watchlist_listingid" : Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user=request.user.username),
"watchlist_count" : len(Watchlist.objects.filter(user=request.user.username))
})
def addwatchlist(request, id):
if request.user.username:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
watchlist.save()
return redirect('listing', id=id)
else:
return render('auctions/watchlist.html', {})
def removewatchlist(request, id):
if request.user.username:
# all_entries = Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user='admin')
# for i in all_entries:
# if i == id:
# removeMe = i
# else:
# removeMe = 'TEST ERROR'
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
watchlist.delete()
return redirect('listing', id=id)
Urls.py :
path("listing/<int:id>", views.listing, name="listing"),
path("watchlist", views.watchlist, name="watchlist"),
path("addwatchlist/<int:id>", views.addwatchlist, name="addwatchlist"),
path("removewatchlist/<int:id>", views.removewatchlist, name="removewatchlist")
The Add/Remove buttons are hosted on listing.html :
{% if user.is_authenticated %}
<p>
{% if watchlisted %}
<button class="btn btn-danger">Remove from watchlist</button>
{% else %}
<button class="btn btn-success">Add to watchlist</button>
{% endif %}
</p>
{% endif %}
Questions :
Am I complicating myself by using Watchlist.watchlist_listingid as a
foreign key for Listing? I have seen quite a few other posts where
people would tend not to use foreign keys at all, though I believe it
might be not optimized.
I could eventually be using a form to envelop those buttons but I
already have a form in my listing.html page (to add comments).
Tackling the data from a form might be easier though I have not
managed to find out how to implement several distinct Django forms on
one HTML page. What do you consider best practice? Forms or direct
link summoning functions?
For "Add to Watchlist", why do I obtain def __str__(self):return f'"{self.title}"'instead of simply adding a new entry in my
Watchlist table?
Finally, for "Remove from Watchlist",why do i receive "id attribute is set to None" when I know for a fact that on my example,the
Listing.id for "Iphone 11 Pro" is 1, and technically speaking,
Watchlist.id (which does not show at all in my model) is 11 based on
the Django Admin page. Even hardcoding 11 to force its deletion off
the Watchlist table still returns this "none" error.
Looking forward to your replies!
Per your specific error, the problem is in this line, which should be clear from the error message:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
id is expecting an int (the id of a Model instance), but you are actually passing it an object instance of your Listing model; filter() returns a queryset, and then .first() returns the first object in that queryset.
That's why the error is telling you that "field id", which expects an int, is instead getting "<Listing: "Gloss + Repair Spray">" -- that's an actual instance of your Listing model.
Solution:
Simply adding .id after the .first() should fix your problem, so you are actually passing the id of that object to the id field:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id)
That should resolve your specific issue; having said that, though, I highly recommend you use forms for these buttons. Currently, you are allowing modification of the contents of your database in an insecure fashion. Any activity that modifies database content should be delivered as a POST request. You can do this with forms quite easily, even if you aren't using any actual input-fields. The button will submit the empty form as a POST request, you can include a csrf token in your form tag in the template, and the db will be modified accordingly. Another option, if you really don't want to use forms, is to use AJAX calls (via jQuery), which can be sent as POST requests without the use of forms. Explaining how to do that is beyond the scope of this response, but there is plenty of info here on SO.
Lastly, if a Watchlist is tied to a User, you should probably consider a different database schema entirely for getting results-- that is, one that actually ties the User model to the item they are adding to their watchlist (it's possible, though, that I'm misunderstanding your intent here, and maybe you don't want that). You could connect the User model and the Listing models via a Many-to-Many relationship, since a User can 'watchlist' many listings, and a listing can be 'watchlisted' by many users.
Fix for : "Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">."
Simply add .id after .first()
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first() = <Listing: "Gloss + Repair Spray">
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id) = 6 (which is the ID in my database)
Fix for : Watchlist object can't be deleted because its id attribute is set to None.
watchlist = Watchlist.objects.get(user=request.user.username, watchlist_listingid=id)
GET the whole row in your table and assign it to a variable before using delete()
Instead of the faulty :
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
Finally, I found out that to use different Django Forms in the same HTML page, you can set the action of the form to a path url in URLS.PY which would then trigger a special functions in Views.py.

Django: How to parse slug from url typed in address bar?

I am trying to simulate Google's behavior where the user types something on the address bar of the browser and the Django server checks for any exact matches in the database. If so, a detailview of the object is rendered. If not an exact match, then a list of matches on substrings are rendered with ListView.
This behavior works fine when the user types into a search form. For instance, when the user just types the letter 'j' in the search form and hits submit, the Django server matches on 3 objects in the data base 'Django, Java, node.js' and renders this list through ListView. If there is an exact match, say the user typed 'java', then the Django server renders details about the object 'java' in a Detail view.
However, I could not figure out how to derive the same behavior when applied to what the user types on the address bar of the browser. If the user happens to type just the exact spelling of an item in the db, the details of the item is rendered, otherwise we get a 404 error.
The relevant segments of the html form, urls.py, and views.py are displayed below
<form method="GET" action="{% url 'searchwiki' %}">
<input class="search" type="text" name="q" placeholder="Search Encyclopedia">
</form>
urls.py :
urlpatterns = [
path("", views.EntryListView.as_view(), name="index"),
path("entries/",views.EntryListView.as_view(),name='entries'),
path("entry/<int:pk>",views.EntryDetailView.as_view(),name="entry_detail"),
path("<int:pk>", views.EntryDetailView.as_view(), name="path_entry-detail"),
# path("<slug:subject>",views.get_obj_orlist),
path("<slug:subject>",views.EntryDetailView.as_view()),
path("random/",views.randompage,name="randompage"),
path("searchwiki/",views.searchwiki,name="searchwiki"),
]
views.py :
from django.urls import path
from . import views
from .models import Entry
from django.shortcuts import get_object_or_404, render, redirect
from django.views import generic
from django.views.generic.edit import CreateView, UpdateView, DeleteView
def searchwiki(request):
searchtoken = request.GET.get('q')
try:
entry = Entry.objects.get(subject=searchtoken)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=searchtoken)
print("Inside exception code. qset size is ",len(entries))
return render(request, 'wikiencyc/searchencyc.html','entries':entries,'searchtoken':searchtoken})
return redirect(entry)
def get_obj_orlist(request):
model = Entry
slug_field = 'subject'
slug_url_kwarg = 'subject'
# below is the solution if it works
slug = kwargs.get(slug_url_kwarg)
try:
entry = Entry.objects.get(subject=slug)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=slug)
return render(request, 'wikiencyc/searchencyc.html', {'entries':entries,'searchtoken':slug} )
return redirect(entry)
class EntryDetailView(generic.DetailView):
model = Entry
slug_field = 'subject'
slug_url_kwarg = 'subject'
The "searchwiki/" path in urls.py and the searchwiki(request) function in views.py work perfectly together for said functionality in response to search form.
As for parsing the slug from the address bar, the EntryDetailView(generic.DetailView) does a perfect job for an exact match of the parsed slug in the database, but responds with a 404 exception to the screen instead of a list of substring matches.
My attempt at replicating the searchwiki function for the addressbar is the function get_obj_orlist(request). It fails because I could not figure out how to get the slug from the address bar and urlconf to said function. It is probably something very simple but after 2 days of searching through the Django server code and docs I am saying AARRGG!!! when I see args and kwargs. Any help is deeply appreciated. I am still struggling with regular expressions, so I would appreciate it if these can be avoided in the solution presentation.
I found the solution. As I had expected, it was a very trivial overlook. Just including subject as a parameter into the function allowed its use in the Entry model for querying purposes. The working function is depicted below.
def get_obj_orlist(request, subject):
model = Entry
try:
entry = Entry.objects.get(subject=subject)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=subject)
return render(request, 'wikiencyc/searchencyc.html', {'entries':entries,'searchtoken':subject} )
return redirect(entry)

Django NoReverseMatch at /school/new-school/

What is wrong with my approach?
When I post new data, I want it to return back to the page with the input fileds empty. But it gives me this error
NoReverseMatch at /school/new-school/
Reverse for 'new-school' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []
This is my model. Please note, reverse_lazy was imported
class SchoolList(models.Model):
name = models.CharField(max_length=15, null=False)
def __str__(self):
return '%s' % (self.name)
def get_absolute_url(self):
return reverse_lazy('new-school')
This is my url.py
url(r'^school-list/$', SchoolListtView.as_view(), name='school-list'),
url(r'^new-school/$', CreateSchoolListView.as_view(), name='new-school'),
url(r'^school(?P<pk>\d+)/update/$', SchoolListUpdate.as_view(), name='update-school')
This is my view for create.
class CreateSchoolListView(CreateView):
template_name = 'school\create_form.html'
model = SchoolList
fields = ['name']
This is how I specified the urls in the template.
Create New School
View all Schools
When the page is displayed, I can click the links and it will go to the correct pages. But when I post a data, the it throws the above error. I have been on this for hours and read many of the answers online. It seems mine is a unique case.
Try adding namespace to get_absolute_url().
def get_absolute_url(self):
return reverse_lazy('school:new-school')
Make sure you import your app's urls in your project's urls with namespace like:
url(r'^school/', include('school.urls', namespace="school"))
to use namespace in your templates like this:{% url 'school:new-school' %}
Or remove namespace:
url(r'^school/', include('school.urls'))
to use url without namespace in template:{% url 'new-school' %}
Using get_absolute_url would be a bad approach in this case, since it's an instance method, designed to get url for single model instance.
If you want to add method to model, you should use something like this:
#classmethod
def get_create_url(cls):
return reverse_lazy('school:new-school')

How do I use DateDetailView with a slug to render a page with a single article?

I am new to Django, and I am trying to have a separate page where I can view individual articles. Currently I have:
#views.py
class ArticleView(DateDetailView):
template_name = 'blog/article.html'
model = Article
date_field = "pub_date"
#I am not sure which one to use
slug_field = "unique_url_suffix"
slug_url_kwarg = 'unique_url_suffix'
and
#urls.py
urlpatterns = [
url(r'^(index\.html)?$',views.IndexView.as_view(),name='index'),
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<slug>[-\w]+)/$',
views.ArticleView.as_view(),
name="article_detail"),
]
and in index.html inside a loop of objects from the Article class:
<h2>{{article.title}}</h2>
I have also tried manually inputting the arguments, like this:
<h2>{{article.title}}</h2>
I keep on getting a "NoReverseMatch at /blog/" error. What am I doing incorrectly?
Edit: On top of the changes recommended for the answer, there was a typo causing problems. It does not affect the answer below, though.
First off, you should not be generating this URL in your template. You should define a get_absolute_url method in your Article model that looks like this:
from django.core.urlresolvers import reverse
def get_absolute_url(self):
# Note - you have to supply each of the date components separately
# because you need to match the URL regex.
return reverse (
'blog:article_detail',
kwargs={'year': self.pub_date.strftime("%Y"), 'month': self.pub_date.strftime("%b"),
'day': self.pub_date.strftime("%d"), 'slug': self.unique_url_suffix}
)
And then in your template:
<h2>{{article.title}}</h2>

Using Slugify in Django urls

I'm trying to create SEO friendly urls where all spaces are replaced with a hyphen.
This is how I'm 'slugifying' the URL by using slugify in Django templates
<a href="{% url 'dj' dj_name=dj.name|slugify %}">
Here is my urls.py
url(r'^top100/(?P<dj_name>[a-zA-Z0-9 \'&-]+)/$', views.dj, name='dj')
This is my view
def dj(request, dj_name):
dj = DJ.objects.get(name=dj_name)
dj_song_list = Song.objects.filter(artist=dj, duplicate=False).order_by('-votes', '-release_date')
return render(request, 'hunt/dj.html', {'dj_song_list': dj_song_list, 'dj':dj}
Now the %20 in the urls has changed to a - but I get the error DJ matching query does not exist.
Also this ignores & in the DJ name. For example it changes the url for the DJ Above & Beyond to www.example.com/top100/above-beyond
You're trying to request an object with its slug instead of its name in database. The slug is a string, compute from the original name, which you can use in URL (because it's SEO-friendly). But you can't request objects with it if you don't have save this slug anywhere in your database. Indeed, it's impossible to retrieve the original name from the slug.
Above & Beyond --> above-beyond --> Above # Beyond }
--> above & beyond } A lot of possibilities...
--> ABOVE - BEYOND }
--> ...
You need to use a SlugField() and get the object wanted according to this new field. Short example:
class News(models.Model):
title = models.CharField('title', max_length=100)
slug = models.SlugField('slug', max_length=100, unique=True)
content = models.TextField('news content')
def get_absolute_url(self):
return reverse('news-view', args=(self.slug, ))
# In the app/urls.py:
from . import views
urlpatterns = [
url(r'^(?P<slug>.+)/$', view.news_detail, name='news-view'),
#...
]
# In the 'news_detail' view (app/views.py)
news = get_object_or_404(News, slug=slug)
In practice, you can use the templatetags slugify if you want to use clean URL like stackoverflow: they're using the ID of the question to retrieve the content from the URL, but there's also the title, which you can change, it'll redirect you anyway.
http://stackoverflow.com/questions/21377984/using-slugify-in-django-urls
^^^^^^^^
ID used ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
slug just for SEO purpose, not
used to retrieve from the db
why don't you use SlugField() in your models ?
Then you can queryset on your slug.
I guess the error comes from the queryset on name instead of a slug.

Categories