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.
Related
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)
I have a django listview working fine.
Its receive url parameters to filter data.
Its paginated.
Now, I want to maintain these data along the user session. (page number and url parameters).
Example:
I'm in the products list view.
I search for 'foo'
I select page 2
And then, I click in any product detail.
The page will redirect to detail view.
When I return to product list view, I whant to keep the search argument 'foo' and selected page 2.
What is the better way to do this?
I'm using Django 2.0.6
Models.py
class Product(models.Model):
name= models.CharField(_('name'), max_length=150)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.0)
Views.py
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
queryset = Product.objects.all()
name = self.request.GET.get('name', None)
if name:
queryset = queryset.filter(name__icontains=name)
return queryset
Urls.py
path('products/', views.ProductList.as_view(), name='product_list'),
For this you have to put the URL as a get request so that you can fetch the get values form the URL and use them in your filter to maintain your selection like:
url/?variable=value
Then in your Django view, you can access this by request.GET.get('variable') and pass this as the context in your HTML render page then use that variable in your filter selection.
Setting variable in session:
For setting the variable in the session you can set it by:
request.session['variable'] = 'value'
and this value can be retrieve by:
if 'variable' in request.session:
variable1 = request.session['variable']
You can refer this docs.
One common trick I use to do this is to use GET parameters and save directly the entire url in session (it saves time compared to saving each individual parameter individually)
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
self.request.session['saved_product_list_url'] = self.request.get_full_path()
....
Then you can use it like this in templates :
product list
Or like this in views :
saved_product_list_url = self.request.session.get('saved_product_list_url')
if saved_product_list_url:
return redirect(saved_product_list_url)
else:
return redirect('product_list')
Also in your filter form you should add a "reset filters" like this :
reset filters
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.
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>
I am trying to implement sharing in my registration-required website. I would like the user to be able to share a page for a certain duration of time (1 day, 1 week, etc.) so that anyone with a special link can access that page. Is this possible in Django?
EDIT: My solution (based on Saurabh Goyal’s answer):
Add a new model to your models.py, something like this:
class ShareKey(models.Model):
location = models.TextField() # absolute path
token = models.CharField(max_length=40, primary_key=True)
creation_date = models.DateTimeField(auto_now_add=True)
expiration_seconds = models.BigIntegerField()
data = PickledObjectField() # custom sharing data
(The data field is optional, and requires django-picklefield).
In your views.py, add a decorator function like this:
def allow_shares(view_func):
def sharify(request, *args, **kwargs):
shared = kwargs.get('__shared', None)
if shared is not None:
if '__shared' not in view_func.func_code.co_varnames[:view_func.func_code.co_argcount]:
del kwargs["__shared"]
return view_func(request, *args, **kwargs)
else: return login_required(view_func)(request, *args, **kwargs)
return sharify
This decorator allows a view to require a login, unless the page is shared.
Decorate any views you want to be shareable with #allow_shares.
Add a new Exception subclass, SharifyError, like this:
class SharifyError(Exception):pass
Also in views.py, add a view to resolve the shared URLs, like this:
def sharedPage(request, key):
try:
try:
shareKey = ShareKey.objects.get(pk=key)
except: raise SharifyError
if shareKey.expired: raise SharifyError
func, args, kwargs = resolve(shareKey.location)
kwargs["__shared"] = True
return func(request, *args, **kwargs)
except SharifyError:
raise Http404 # or add a more detailed error page. This either means that the key doesn’t exist or is expired.
Add a url to urls.py, like this:
urlpatterns = patterns('',
# ...
url(r'^access/(?P<key>\w+)$', views.sharedPage, name="sharedPage"),
# ...
)
Finally, add URLs to create a shared link, and implement the view like this:
# in imports
from django.utils.crypto import get_random_string
def createShare(request, model_id):
task = MyModel.objects.get(pk=model_id)
key = ShareKey.objects.create(pk=get_random_string(40),
expiration_seconds=60*60*24, # 1 day
location = task.get_absolute_url(),
)
key.save()
return render(request, 'share.html', {"key":key});
(Your share.html template should look something like this):
{% extends 'base.html' %}
{% block content %}
<h1>Sharing link created.</h1>
<p>The link is {{ base_url }}{% url 'taskShared' key.pk %}. It will be valid until {{ key.expiration_date|date:"l, N dS" }} at {{ key.expiration_date|time:"g:i a" }}.</p>
{% endblock %}
This will require users to login to view the decorated pages unless they have entered a key.
Yes, you can do that simply by having one additional field in dB which will represent the last datetime till when page is accessible by all. Then whenever someone accesses it, just check if current datetime is before or equal to value of that field, if yes, let them access, if no, deny access.
When user makes request to make page accessible, create the special link and update the field accordingly.
Make sure to handle routing for special link you generate on every request.
Edit-
To handle things using keys, you can take following steps-
1) generate a random key whenever user requests for special link,
2) store the key and corresponding latest datetime of access for page in db
3) assuming main url for your page was '/mypage/', and your urls.py is something like this-
from django.conf.urls import patterns, url
from apps.myapp import views as myapp_views
# See: https://docs.djangoproject.com/en/dev/topics/http/urls/
urlpatterns = patterns(
'',
url(r'^mypage/$', myapp_views.MyPageView.as_view(), name='mypage'),
)
you add another url, something like this-
from django.conf.urls import patterns, url
from apps.myapp import views as myapp_views
# See: https://docs.djangoproject.com/en/dev/topics/http/urls/
urlpatterns = patterns(
'',
url(r'^mypage/$', myapp_views.MyPageView.as_view(), name='mypage'),
url(r'^mypage/(?P<key>\w+)/$', myapp_views.MyPageView.as_view(), name='mypage_special'),
)
What above results in is that a url '/mypage/any_alphanumeric_key/' will also redirect to your page view.
4) In your views, access the key, fetch the latest datetime of access for that key from dB, and if valid, give access else deny access.