I have a database containing books. On mysite.com/books, this database is presented in table form, with the title and the author displayed as columns and books are rows.
When a user clicks on a book title, it leads to a details page: mysite.com/books/slug-of-specific-book. I have managed to get this working fine, but I'm having trouble figuring out how to access the information of the specific book on the details page. Namely, I'm uncertain if I'm configuring my view for this correctly. I'd like to be able to access something like {{ book.name }} in the template page for the details page and have that display at the top, for instance.
models.py
class Book(models.Model):
name = models.CharField(max_length=200)
author = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return reverse("Details", kwargs={"slug": self.slug})
urls.py
url(r'^books/(?P<slug>[-\w]+)/$', inventory.ShowDetails.as_view(), name="Details"),
views.py
class ShowDetailedListing(ListView):
template_name = "detailed_book.html"
def get(self, request, *args, **kwargs):
return render(request, self.template_name)
Could someone please give me some pointers on how to write this view so that I can access something like {{ book.name }} in "detailed_book.html"? I tried seearching online for this (as it seems really simple), but couldn't quite find anything, probably because I'm not sure how to phrase this.
DetailView might be more appropriate than list view since it seems you want to display information on 1 object
you can set the context_object_name to book
Using detail view should automatically map the slug field in your url to the slug on your Book model
class BookDetailView(DetailView):
model = Book
context_object_name = 'book'
Related
I have List of articles to be displayed as per date. Every User can mark any article as his favourite.
If a user is logged in, then he should be able to see some indication whether the article is already marked as his favourite or not.
the following are my models:
class Favourite(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
article = models.ForeignKey(Article, null=True,on_delete=models.CASCADE)
class Meta:
unique_together = ('user', 'article')
class Article(models.Model):
title = models.CharField(max_length=256)
intro_image = models.ImageField(null=True, blank=True, upload_to=doc_hash)
description = models.TextField(null=True, blank=True)
I was thinking for every article get all the user ids who marked that article as favourite and then later check it at front end with the current user.
But if some article is marked as favourite by 1000 users , then unneccessarily i will have to get the data of 1000 users along with that article which is too much.
Is there a way if i pass the user i can get the favourite data only with respect to that user for each article so that i can save both on queries and amount of data to be passed to front end.
Either do that in your view's context or as a custom template tag.
The examples below are dry-coded, so they might have silly bugs.
In a view (assuming a class-based view):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['is_favourite'] = Favourite.objects.filter(user=self.request.user, article=self.object).exists()
return context
Usage:
{% if is_favourite %}Yay, you like it!{% endif %}
or a template tag:
#register.simple_tag(takes_context=True)
def is_favourite(context, article):
request = context['request']
return Favourite.objects.filter(user=request.user, article=article).exists()
Usage:
{% is_favourite article as fav %}
{% if fav %}Yay, you like it!{% endif %}
Edit
For a ListView, you can do something like
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['favourited_ids'] = set(Favourite.objects.filter(user=self.request.user, article__in=context['object_list']).values_list('article_id', flat=True))
return context
and use it
{% if article.id in favourited_ids %}Yay, you like this article!{% endif %}
I assume that you need a flag to state if the user has already marked the article as favourite or not, suppose there are 100 articles and out of that a user has marked 40 articles as favourite then when the data would be sent then 100 articles data is sent with 40 articles having flag as read TRUE and rest as FALSE.
Following is the SQL equivalent, which you can convert to Django ORM as per your need. 'xyz' is the user id for which you need to show the articles
SELECT article.*,
CASE
WHEN read.article_id IS NOT NULL then TRUE
ELSE FALSE
as read_flag
from Article as article
left join
(select article_id from Favourite where user_id = 'xyz') as read
on article.id = read.article_id
I found the below answer from: https://stackoverflow.com/a/51889455/2897115. Using annotations and subquery. Django can be faster using annotations and subquery which i read from. https://medium.com/#hansonkd/the-dramatic-benefits-of-django-subqueries-and-annotations-4195e0dafb16
I am putting the solution given in https://stackoverflow.com/a/51889455/2897115. Using annotations
qs = Post.objects.all()
sub = Like.objects.filter(post__id=OuterRef('pk'), user=request.user)
values = qs.annotate(
date=Func(F('date'), function='db_specific_date_formatting_func'),
current_user_like=Exists(sub)
).values('text, 'date', 'current_user_like')
Hi i'm sure this had a simple solution but i cant find it! It must be required ALL the time!
To learn django i am writing simple app for me to log my learning points. So i have two models:
class Topic(models.Model):
title = models.CharField(max_length=40)
def __unicode__(self):
return self.title
class Meta():
ordering = ['title']
class Fact(models.Model):
note = models.CharField(max_length=255)
topic = models.ForeignKey('Topic')
def __unicode__(self):
return self.note
class Meta():
ordering = ['note']
I have template and url that will list ALL the topics.
When i see that list i want to be able to click on it [which i can do] and have that topic and all the facts linked to it (thourgh the foreign key appear) [would that technicaly be described as filtered query set of child objects?] I am using detailview.
url
url(r'^(?P<pk>\d+)/$', TopicDetailView.as_view(), name='facts'),
Here is the code of the detail view. Know i knows it knows the pk as it shows the right page when i take out the extracontext filter (and just take .all()). But i cant ref it no matter how many ways i try. I'd like something like this...
class TopicDetailView(DetailView):
model = Topic
template_name = 'study/topic_facts.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(TopicDetailView, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['fact_list'] = Fact.objects.filter(topic='pk')
return context
I can do this if i put some logic and a filter in the template but that doesn't seem very proper to me and i feel i must be able to do this easily by adding the right extra context.
Help some poor newbie out! Many Thanks.
'pk' is just a string. You mean self.kwargs['pk'].
But actually you don't want to do this at all. The super class already adds the Topic object to the context: and you have a relationship between Topic and Fact. You can traverse this relationship in the template:
{% for fact in topic.fact_set.all %}
...
{% endfor %}
so you don't need to override get_context_data.
I'm working on a DIY application (Django 1.5) and I've reached a roadblock. The main models involved are Guide, Tool, Item, Step. A Guide can have many Tools and Items, and a Tool or Item can belong to many Guides. The same goes for a Step - it can have many Tools and Items, and a Tool or Item can belong to many Steps. A Guide has many Steps and a Step belongs to a Guide.
Guide many-to-many Items
Guide many-to-many Tools
Guide one-to-many Steps
Step many-to-many Items
Step many-to-many Tools
The roadblock...
At the Guide-level, I want the Tool and Item options to be limitless. But at the Step-level, I want the Tool and Item options to be limited to those assigned to the Guide it belongs to. Basically, when creating/editing a Step, I want to list checkboxes for all the Tools and Items available through the Guide. The user selects those that are needed for the current Step. Each Step will have different combinations of Tools and Items (thus the need for checkboxes).
I discovered the ModelMultipleChoiceField for the Step's ModelForm class. There I can specify a queryset. BUT, how do I gain access to the instance of the Guide model to retrieve its Tools and Items so that I can properly build selections? I would like to provide queries similar to what you would do in a View...
Guide.objects.get(pk=n).tools.all()
Guide.objects.get(pk=n).items.all()
How can I achieve that via ModelMultipleChoiceField? I hope I was able to explained this clearly.
Thanks in advance for any help.
class Tool(models.Model):
name = models.CharField(max_length=100)
...
class Item(models.Model):
name = models.CharField(max_length=100)
...
class Guide(models.Model):
models.CharField(max_length=100)
description = models.CharField(max_length=500)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
...
class Step(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
guide = models.ForeignKey(Guide)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
EDIT: 5/2
After further reading, it looks like I have to override the __init__ method of ModelMultipleChoiceField, where I gain a reference to self.instance, allowing me to create my query like, self.instance.guide.tools.all() and self.instance.guide.items.all(). And then create the fields via fields['field_name'].
I'm at work now so I won't be able to try this out until later tonight. I'll report back my findings.
What I ended up doing is the following. I defined a method in my ModelForm class for creating the ModelMultipleChoiceField. The reason is at the point of requesting the Create Step page, there is no Guide associated with the Step, not until you save (POST...assuming validation is successful). But I do have access to the slug field of the Guide that the Step will be created for. I get if from the URL. And the slug field is unique in my app. So I pass the slug from my view to the form via the new method I created. From there, I'm able to get the tools assigned to the guide and make those options available on the form, in my template.
forms.py
class NewStepForm(forms.ModelForm):
...
def create_tools_field(self, slug):
self.fields['tools'] = forms.ModelMultipleChoiceField(
queryset=Guide.objects.get(slug=slug).tools.all(),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'unstyled'})
)
...
views.py
class NewStepView(View):
form_class = NewStepForm
initial = {'key': 'value'}
template_name = "guides/guide_step_new.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(NewStepView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
slug = kwargs.get('slug')
form = self.form_class(initial=self.initial)
form.create_tools_field(slug)
return render(request, self.template_name, {'form': form})
In my project I have two different types of users: teacher and student, each with their own profile data.
After searching for the best approach it seems the way to go forward is using multi-table inheritance:
class BaseProfile(models.Model):
user = models.OneToOneField(User)
profile = models.CharField (max_length=10, choices={'teacher', 'student'})
# other common fields
class Teacher(BaseProfile):
# teacher specific fields
class Student(BaseProfile):
# student specific fields
And in settings.py: AUTH_PROFILE_MODULE = myapp.BaseProfile.
Now I want to implement the same functionalities as in django-profiles:
create profiles
edit profiles
display profiles
I have a good idea how to do the edit and display part when I have the correct value in the field profile of BaseProfile.
The problem:
Now I want the creation of the profile to be done automatically (and in the right db: Teacher or Student) directly when a user is created by using a signal.
The field profile should contain the value "student" when the user registers through the site via the registration form. The value should be "teacher" when the admin creates a new user through the admin interface.
Anyone an idea how I can accomplish this? Probably I need to write a custom signal, something like the below, and send it from the User Model, but didn't found a working solution yet:
def create_user_profile(sender, instance, request, **kwargs):
if request.user.is_staff:
BaseProfile(user=instance, profile='teacher').save()
else:
BaseProfile(user=instance, profile='student').save()
Other and better approaches are of course also welcome!
Thanks!
In my opinion it isn't a good approach.
I would recommend doing 1 unified profile which will contain an option:
user_type = models.CharField(choices=[your_choices], max_length=4)
Then in models you would create two forms - 1 for teacher and 1 for student.
class ProfileFOrm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BaseProfileForm, self).__init__(*args, **kwargs)
for name in self.fields:
self.fields[name].required = True
class TeacherProfile(ProfileForm):
class Meta:
model = Profile
fields = ('your_fields')
class StudentProfile(ProfileForm):
class Meta:
model = Profile
fields = ('school')
That's just my idea for that :)
Edited
Profile edition:
view.
def profile(request):
p = get_objects_or_404(ProfileModel, user=request.user)
return TemplateResponse(request, 'template.html', {'profile': p})
In models we need a function to check if user is a student or a teacher, so:
class Profile(models.Model):
... your fields here...
def get_student(self):
return self.user_type == 1
In templates:
{% if profile.get_student%}
>>>>get all data for students ex: <<<<
{{profile.name}}
{% endif %}
{% if profile.get_teacher %}
....
I know this is a very basic concept in Django, and I have tried the tutorial but it is not working. I am working on a comic book database with the models set like this (at least, a sample of two of the models):
Class Title(models.Model):
title = models.CharField(max_length=256)
vol = models.IntegerField("Vol.")
slug = models.SlugField(blank=True, null=True)
#desc = models.CharField(max_length=256)
class Meta:
ordering = ['title']
def get_absolute_url(self):
return "/comics2/title/%s" % self.slug
def __unicode__(self):
return self.title
class Issue(models.Model):
title = models.ForeignKey(Title)
number = models.IntegerField(help_text="Enter the number only. Do not include the hashtag.")
writer = models.ManyToManyField(Creator)
What I am trying to do is create a page that shows a list of all the issues within that Title.
But, I have it setup in the views like this:
class AstonishingXMenIssueListView(ListView):
context_object_name = "astonishing_list"
queryset = Issue.objects.filter(title__title="Astonishing X-Men").order_by("number")
template_name = "comics2/astonishing_list.html"
My urls.py look like this:
(r'^comics2/title/(?P<title_slug>[-\w]+)/$', AstonishingXMenIssueListView.as_view(
)),
Of course, going to /uncanny-xmen-v1/ shows the same thing as the Astonishing link above.
Obviously, this is not a practical way to list issues by title for future issues and titles, so I need it setup so that I don't have to individually do this. Now, I have tried following the Django generic views tutorial, but I got an index tuple error.
I've tried this, but it doesn't work. This is what gets me the index tuple error.
class IssuesByTitleView(ListView):
context_object_name = "issues_by_title_list"
template_name = "comics2/issues_by_title.html",
def get_queryset(self):
title = get_object_or_404(Title, title__iexact=self.args[0])
return Issue.objects.filter(title=title)
Any ideas? And can someone please reply in baby-language, as I am new to Django and Python, so simply telling me to look at the Tutorial again isn't going to help. So, maybe writing out the code would help! Thanks!
Generally, your IssueByTitleView is the right way to do it. But as you use named groups in your URL regex (the (?P<title_slug>[-\w]+) part of your URL), you have to access the URL parameters through self.kwargs instead of self.args. Also, you have to filter on the slug field, not the title field:
title = get_object_or_404(Title, slug=self.kwargs['title_slug'])