I have been trying for too long now to get my urls working with my simple models and view. There is something I am definitely not understanding.
I wish to create a url similar to:
.../department/team,
where I do not use the PK of department object but the name instead.
My model looks like:
class Department(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
def __str__(self):
return 'Department: ' + self.name
class Hold(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
my view looks like (UPDATED):
class IndexView(generic.ListView):
template_name = 'index.html'
context_object_name = 'departments_list'
def get_queryset(self):
return Department.objects.all()
class DepartmentView(generic.DetailView):
model = Department
template_name="depdetail.html"
slug_field = "name"
slug_url_kwarg = "name"
my url looks the following: UPDATED
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<name>', views.DepartmentView.as_view(), name='depdetail')
]
and finally my html:
<h1> Hello there {{ object.name }} student</h1> </br>
<b> Choose your team:<b> </br>
however i keep getting page not found or must be slug or pk..
I hope someone can help me out so I can wrap my head around this.
UPDATED
It works now :) Thank you for the replies.
By default Django will look for a pk or a slug field when you use a DetailView. You must override get_object() method to change this behaviour:
get_object() looks for a pk_url_kwarg argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a slug_url_kwarg argument, and performs a slug lookup using the slug_field.
That being said, your approach has other problems. It is always better to use a slug instead of a name for other reasons. For example, name is not guaranteed to be unique and also it may have characters which are not URL safe. See this question for a detailed discussion on how to use slug fields.
Related
i want to filter posts based on views e.g view>100 that is to filter course if only the view is greater is 100 views but it keeps showing this error
SyntaxError: positional argument follows keyword argument. the way i am filtering the posts is the issue but i don't know the right way to do this. I have a field views = models.In... in my models.py, so that is why i am trying to filter course like course = Course.objects.filter(views>100) then it shows the error
models.py
class Course(models.Model):
course_title = models.CharField(max_length=100, null=True, blank=True)
slug = models.SlugField(unique=True)
views = models.IntegerField(default=0)
views.py
def index(request):
pop_courses = Course.objects.filter(course_publish_status="published", views>100).order_by('?')
You need to use the __gt lookup to perform this filter
Course.objects.filter(course_publish_status="published", views__gt=100).order_by('?')
You can filter with the __gt lookup [Django-doc]:
Course.objects.filter(
course_publish_status='published',
views__gt=100
).order_by('?')
Whenever I click on job_title link in index.html, django redirects me to detail url and from there it redirects to DetailView in views.py file and finally it opens detail.html file.
Same thing I am doing for category link. But it gives me an error: No job found matching the query. (Page not Found Raised by: JobPanel.views.DetailView). I'm getting right slugs from the database to index.html.
urls.py
path('<slug:detail_slug>/', DetailView.as_view(), name='detail'),
path('<slug:cat_slug>/', CategoryView.as_view(), name='category'),
views.py
class DetailView(generic.DetailView):
model = Job
template_name = 'JobPanel/detail.html'
slug_url_kwarg = 'detail_slug'
slug_field = 'slug'
class CategoryView(generic.DetailView):
model = Job
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'
index.html
{{ job.job_title }}
<br>
{% for cat in job.categories.all %}
{{ cat }}
{% endfor %}
models.py
class Category(models.Model):
category = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.category
class Job(models.Model):
job_title = models.CharField(max_length=250)
job_description = models.TextField()
slug = models.SlugField(unique=True)
Short answer: make non-overlapping patterns.
The two patterns you defined completely overlap. Indeed, if you have a url with /some-category-slug, then that url will match the <slug:detail_slug>/ pattern and thus trigger the first view. The fact that another path also matches is irrelevant, since Django always will trigger the first view in the list that matches.
The best way to solve this is make non-overlapping patterns, like:
path('detail/<slug:detail_slug>/', DetailView.as_view(), name='detail'),
path('category/<slug:cat_slug>/', CategoryView.as_view(), name='category'),
If you now generate a url for a category, it will look like category/my-category-slug. This can not match with the first path(..) since that requires that the path starts with detail. The opposite holds as well.
Note that you forgot to set the model of your CategoryView correctly, it likely should be:
class CategoryView(generic.DetailView):
model = Category # change to Category
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'
You define the CategoryView like this:
class CategoryView(generic.DetailView):
model = Job
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'
which will look up the following:
Job.objects.get(slug=cat_slug)
Looking at your models this seems wrong if cat_slug is supposed to point at job.categories.slug rather than job.slug.
You have left out the code that creates the relation between Job and Category. It seems to be a 1-to-many relation because of job.categories in your template code.
In this case, your CategoryView should either:
subclass ListView, keeping model = Job and using slug_field=category__slug
or subclass DetailView, using model=Category, keeping the rest as is
EDIT:
Willem's point of overlapping URLs is of course the other issue here.
If you need URLs without nested paths (/:slug/ instead of /job/:slug/) - you can also use a pattern like /job-:slug/.
I'm trying to get the category of a skill in a template.
From the post I read, I can't directly get information from a foreign key in a template.
Instead, I add a function on CharacterSkill models to get the Skill category
Models.py
class Character(models.Model):
name = models.CharField(max_length=70)
class Skill(models.Model):
name = models.CharField(max_length=70)
cat1 = '01'
SKILLSET_CHOICE = ((cat1:'cat1'))
skillset_choice = models.CharField(
max_length=2,
choices = SKILLSET_CHOICE,
default='',
blank=True,
null=True,
)
class CharacterSkill(models.Model):
character = models.ForeignKey(Character, on_delete=models.CASCADE)
skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
def skillcategory(self):
return Skill.objects.get(id = self.skill).skillset_choice
Template
skillsetchoice {{item.skillcategory}}
But I get an error :
Exception Type: TypeError
Exception Value:
int() argument must be a string or a number, not 'Skill'
I tried to inpect value with the shell console where I can get back the category id but when I use it in template, nothing is working
Hope you can help me!
This has nothing to do with calling it from the template.
self.skill is already an instance of Skill. There is no need to query that model explicitly.
def skillcategory(self):
return self.skill.skillset_choice
And in fact this method is pretty pointless; you certainly can do it directly in the template:
skillsetchoice {{ item.skill.skillset_choice }}
Replace self.skill with self.skill.id (in older versions of Django your code would work by the way):
def skillcategory(self):
return Skill.objects.get(id = self.skill.id).skillset_choice
But this is much better:
def skillcategory(self):
return self.skill.skillset_choice
But do you really need this method? You can use:
{{ item.skill.skillset_choice }}
If you want to display cat1, then (get_foo_display):
{{ item.skill.get_skillset_choice_display }}
UPDATE #2
Status: Still not solved
Updated: Thurs. Dec. 18, 11:30 a.m.
I'm currently using FullArticle.objects.order_by('?').first() to get a random article from my database, but it's not working. There is probably something missing from my models, view or url.py that's missing.
models.py
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class FullArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(publish=True)
class FullArticle(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=150)
slug = models.SlugField(max_length=200, unique=True)
pubDate = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
category = models.CharField(max_length=150)
heroImage = models.CharField(max_length=250, blank=True)
relatedImage = models.CharField(max_length=250, blank=True)
body = models.TextField()
publish = models.BooleanField(default=True)
gameRank = models.CharField(max_length=150, blank=True, null=True)
objects = FullArticleQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("FullArticle_detailed", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Blog entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pubDate"]
views.py
from django.views import generic
from . import models
from .models import FullArticle
# Create your views here.
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
randomArticle = FullArticle.objects.order_by('?').first()
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns(
'',
url(r'^$', views.BlogIndex.as_view(), name="list"),
url(r'^(?P<slug>\S+)', views.BlogDetail.as_view(), name="detailed"),
)
Section in list.html that I want to be random
<div class="mainContent clearfix">
<div class="wrapper">
<h1>Top 10 Video Games</h1>
{% for article in object_list|slice:":1" %}
<p class="date">{{article.pubDate|date:"l, F j, Y" }}</p> | <p class="author">{{article.author}}</p>
<img src="{{article.heroImage}}" alt="" class="mediumImage">
<p class="caption">{{article.body|truncatewords:"80"}}</p>
{% endfor %}
I assume that FullArticle.objects.order_by('?')[0] will give me a
random item from my class of FullArticle. But, let's say that out of
my model, I only want data associated with the specific parts of the
article: title, author, heroImage and body. How would I go about doing
that?
To get specific fields of an object, use values or values_list. The first will return dictionaries, the second tuples:
FullArticle.objects.order_by('?').values('title','author','heroImage','body').first()
The above would result in something like:
{'title': 'Foo', 'author': 'Foo Author', ... }
I've also tacked on your suggestion of random =
FullArticle.objects.order_by('?')[0] called it "random" instead.
Not sure what this is about, but try to avoid shadowing built-in libraries, like random.
1) Actually you almost did it.
try:
article = FullArticle.objects.order_by('?')[0]
except IndexError:
article = None
2) You could use this in models.py as well as in views.py. IMHO there is no need to extract this string to separate method so I would write this code wherever I need it.
3) Better use ORM don't convert db result to list to choose first item. This is can be really memory and CPU expensive.
Getting a random article would usually be done in a view, or as a modelmanager method, or as a class method. Fullarticle.random should not be a class attribute. That will not work as you expect.
# Used in a view.
article = FullArticle.objects.order_by('?').first()
# you can also make a random() method in your model manager.
def random(self):
return self.get_queryset().order_by('?').first()
# or as a class method inside FullArticle
#classmethod
def random(cls):
return cls.objects.order_by('?').first()
I'm not quite sure what exactly you mean by this.
I only want data associated with the specific parts of the article: title, author, heroImage and body. How would I go about doing that?
To access specific attributes you do this:
title = article.title
author = article.author
If you don't need to use article.category, just don't access it.
from django.views.generic import DetailView
from books.models import Publisher, Book
To pass data from your (class based) View to the template it has to be added to the context.
Here's an example from the official documentation:
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
source: https://docs.djangoproject.com/en/1.7/topics/class-based-views/generic-display/#adding-extra-context
Lots of people find Class Based Views in Django to be a bit confusing. I would recommend that you understand how function based views work before you start doing anything complicated with CBVs.
I am trying to create a delete function for my Workout model.
This is the model:
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def delete(self):
return reverse("delete_workout", kwargs = {'workout_id': self.workoutID})
Next I have the view:
def delete_workout(request, workout_id):
workout = get_object_or_404(Workout, workoutID = workout_id)
print(workout)
if request.user != workout.created_by:
return HttpResponse('Not ur workout')
else:
workout.delete()
return HttpResponseRedirect('/')
This is the url:
url(r'^(?P<workout_id>\d+)/delete/$', views.delete_workout, name='delete_workout'),
And finally the html:
<a href='{{ instance.delete }}'>
<button>Delete Workout</button>
</a>
I'm not getting any errors in the console, which is why I don't know what is going wrong.
You are overriding delete method of the class just for getting the delete url. You will get the url by url function in the template like {% url delete_workout instance.workoutID %}. So remove the delete function from the model change your html href url. Leave the view and url as the same. No issues there
class should be
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
And your html should be
<a href='{% url delete_workout instance.workoutID %}'>
<button>Delete Workout</button>
</a>
NOTE: django model itself adds id for each table, so you dont have to specify it as you did workoutID = models.AutoField(primary_key=True).
By default each model will have a id field just like id = models.AutoField(primary_key=True)
If you consider removing the workoutID then the model becomes
class Workout(models.Model):
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
and the html will be
<a href='{% url delete_workout instance.id %}'>
<button>Delete Workout</button>
</a>
Django has all the tools for you under the hood. Don't reinvent the wheel. You can refactor and simplify your code.
First remove the method delete in Workout.
Second, replace your function-based-view with a class-based-view:
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from django.http import Http404
from .models import Workout
class WorkoutDeleteView(DeleteView):
model = Workout
success_url = reverse_lazy('delete_workout')
def get_object(self):
obj = super().get_object()
if obj.created_by != self.request.user:
raise Http404
return obj
A workout can be deleted only by its author. In success_url you specify the target where the user should be redirected after deleting.
Just adapt slightly your urls.py (pay attention to the emphasised part):
url(r'^(?P<pk>\d+)/delete/$', views.WorkoutDeleteView.as_view(), name='delete_workout'),
EDIT:
You can name your views as you please, however it would be better to follow already well established conventions. Thus the names for the class based views should be workout-list, workout-detail, workout-create, workout-update and workout-delete.