I'm new to learning Django and I am working on a page that just displays a users profile and reviews written for that user (both in different models).
The part that I'm a little unsure of is how to link different tables or models. What I'm trying to do is extremely basic. Pretty much as soon as you click on the name of a user, it goes to a profile page which also displays all of that users reviews on the bottom. I'm wondering if I can use a variable in place of 'name=ben' and change it to something like 'name=user_you_clicked_on'.
Hopefully what I'm asking makes sense and maybe I can be pointed in the right direction. I was told to try using a foreign key, but I'm not sure that's what I'm trying to do.
from django.shortcuts import render
def index(request):
profile_info = User.objects.filter(name=‘ben’)
context = {‘profile_info’: profile_info}
latest_reviews = UserReview.objects.filter(name=‘ben').order_by('-pub_date')[:5]
context = {‘profile_info’: profile_info, 'latest_reviews': latest_reviews}
return render(request, 'randomtemplate.html', context)
You can pass username in the URL (ID would better but let's not overcomplicate this):
urlpatterns = patterns('',
url(r'^user/(?P<username>\w+)/', 'index'),
)
Then your view could look like this:
def index(request, username):
profile_info = User.objects.filter(name=username)
In case if you still want to 'connect' two tables. In models.py I would write:
from django import models
class User(models.Modle):
name = models.CharField(max_length=30)
#add more fields here
class UserReview(models.Model):
user_id = models.ForeignKey(User)
#add more fields here
This will create two tables in your db (User and UserReview) where Userreview is connected to User via a foreign key(one user(id) -> many reviews(user_id1; user_id1; user_id2 etc.)). By default this foreign key is the auto-generated id field of User table (by default id fields, which are also primary keys in this example, are auto-generated for each table, unless specified otherwise).
I hope it helps
Related
I have a django page that displays a list of links. Each link points to the detail page of the respective object. The link contains the pk/id of that object (something like ../5/detailObject/). The list is generated on the backend and has some filtering baked into it, e.g. only generate a link if that object has state x, etc.
Clicking on the links works, but users can still manipulate the url and pass a valid link with an incorrect state (a wrong pk/id is being handled with the get or 404 shortcut).
What is the best practice for handling this kind of scenario with django? Should that kind of filtering be placed in the object's model class instead of using function-based views as I do now?
Function based view:
If you want to restrict a set of objects to a particular user (for instance a user's orders), then you would need to set up the Order model to foreign key to the User model and then look up the order by both id and user:
views.py:
def get_order(request, id=0)
if request.method == 'GET':
try:
order = Order.objects.get(user=request.user, pk=id)
except Order.DoesNotExist:
return redirect(...)
And set up a url to handle:
url(r'^order/(?P<id>\d+)/$', views.get_order, name='get_order_by_id'),
As far as adding a slug field on the model after the fact, set up a second url:
url(r'^order/(?P<slug>[\w-]+)/$', views.get_order, name='get_order_by_slug')
And change the above view logic to first do a lookup by pk if pk is greater than 0 and then redirect back to the function using the slug from the looked up order (this assumes all looked-up records have slugs):
def get_order(request, slug='', id=0)
if request.method == 'GET':
try:
if id > 0:
order = Order.objects.get(user=request.user, pk=id)
return redirect(reverse('get_order_by_slug'), permanent=True, slug=order.slug)
order = Order.objects.get(user=request.user, slug=slug)
except Order.DoesNotExist:
return redirect(...)
You should also put unique=True on the slug field and ensure that the user is authenticated by placing the #login_required decorator on your view.
To restrict orders by a particular status, you could:
Create a set of statuses for your Order model, and then you could:
Pass a value for a kwarg in the view when you filter, or
Create a custom manager on the Order model
There are several ways you could create your statuses:
as a set of choices on the Order model
use the SmartChoices library
as a database field
If you create choices on the Order model, it could be something like this:
class Order(models.model):
STATUSES = (
('PLCD', 'Placed'),
('INTR', 'In Transit'),
('DLVR', 'Delivered')
)
status = models.CharField(max_length=4, default='', choices=STATUSES)
An acquaintance who is a very seasoned Django professional told me about the SmartChoices library. I have not used it yet but would like to try it at some point. The database field option would be my least preferred way of doing this because that seems to me like moving programming variables into the database; however, it would work.
I'm using Django 2.0 type of urls, and I have urls with multiple variables in them, with the same name. I'm using also ClassBasedView
path('/companies/<int:pk>/products/<int:pk>/', AccountCompanyProductDetailView.as_view()
I'm using pk because is the primary key and CBV will know how to use it (similar for other Model fields).
If I use other names, CBV will not know what to search.
In a CBV how can I get the parameters and know which is which. ?
How Django knows pk from which Model I need in each position ?
Django does not know how to handle this. You need to rename your parameters and access them in your CBV.
This could look like the following:
urls.py:
path('/companies/<int:pk1>/products/<int:pk2>/', AccountCompanyProductDetailView.as_view())
views.py:
class AccountCompanyProductDetailView(DetailView):
model = Product
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
company = get_object_or_404(Company, pk=pk1)
product = get_object_or_404(Product, pk=pk2)
return product
You would need to do this in other views too. Override the according methods like get_queryset. Access the kwargs as shown above.
I have just completed the Django tutorials, and while excited about learning more, I am by no means proficient. I guess you could say that I don't know enough to be dangerous at this point.
Let's say that I have a database of music. I have an Artist model, an Album model, a Genre model, and a Song model. What I would like to be able to do is display albums (or even artists) based on given filters; so my front-end would display a list of albums and provide a means to filter the list. A "Jazz" link, for instance, would only display Jazz albums. Simple enough.
I can think of a couple ways to accomplish this, but I would like to start out on the right foot...to begin forming "best practice" Django methods. One way I can think of would be to write views...such that /albums/jazz would only display jazz. Another way would be to write model-level methods that filter the albums. Here I get a little fuzzy on how I would actually implement such a filter, however.
Will someone please give me broad overview of how this task is best accomplished?
Assuming you know how to structure an app within a project (i.e. what the tutorial teaches) you can work along this example with example models.py, urls.py and views.py for your sample app myapp.
Example models.py:
class Genre(models.Model):
name = models.CharField(unique=True) # set name to be unique
...
class Album(models.Model):
genre = models.ForeignKey(Genre)
...
Example urls.py:
urlpatterns = patterns('',
...
url(
r'^albums/(?P<genre>[-\w]+)/$',
ListAlbumsByGenreView.as_view(), name='list_albums_by_genre_view'
),
...
)
Note the genre parameter as the only argument in the URL pattern.
Example views.py using ListView:
from django.shortcuts import get_object_or_404
from django.views.generic.list import ListView
from myapp.models import Album, Genre
class ListAlbumsByGenreView(ListView):
model = Album
def get_context_data(self, **kwargs):
context = super(ListAlbumsByGenreView, self).get_context_data(**kwargs)
# fetch the genre; if genre not found, an HTTP 404 is returned
genre = get_object_or_404(Genre, name=kwargs['genre'])
# filter the albums by genre
context['albums'] = Album.objects.filter(genre=genre)
return context
The above ListView puts albums in your HTML template's context; this contains the list of albums filtered by genre.
The individually imported functions used above are all beautifully documented in the Django docs.
In my current project, I'd like to CRUD users OUTSIDE of django's Admin interface.
Let's explain my question as following:
1- I'm using the UserProfile for storing additional attributes for users (their schools, birthday, etc.)
2- The problem is that by deleting a user, I can delete the profile rather than actual User.
Please take at the code for Listing and Deleting Users:
def user_list(request):
''' Shows all of Students '''
return object_list(request,
queryset = UserProfile.objects.all() ,
template_name = 'user_list.html' ,
template_object_name = 'student'
)
def user_delete(request , id):
''' Deletes a student based on his/her ID '''
return delete_object(request,
model = UserProfile ,
object_id = id ,
template_name = 'delete_student.html' ,
post_delete_redirect = reverse("user_list")
)
It looks normal that I'm deleting UserProfile rather than User. But I intended it to be a proxy to actual User. Do I miss something here ?
3- Generally speaking, should I reference each models to User or UserProfile ? For example assume that I have a model for Course. Which of these is the correct way?
class Course(models.Model):
#stuff
student = models.ForeignKey(Urer)
# OR ??
student = models.ForeignKey(UserProfile)
It looks normal that I'm deleting UserProfile rather than User. But I intended it to a proxy to actual User
Why not delete the User directly? By default, Django will CASCADE DELETE to get rid of the UserProfile as well.
Generally speaking, should I reference each models to User or UserProfile
I think this is more a question of personal preference, but I usually tie to User directly as it saves a step when getting to the object you want (you don't need to do user.get_profile().student and can call user.student instead). It also makes more sense to me anyway: the student is a property of the user, not the user's profile.
If a django model contains a foreign key field, and if that field is shown in list mode, then it shows up as text, instead of displaying a link to the foreign object.
Is it possible to automatically display all foreign keys as links instead of flat text?
(of course it is possible to do that on a field by field basis, but is there a general method?)
Example:
class Author(models.Model):
...
class Post(models.Model):
author = models.ForeignKey(Author)
Now I choose a ModelAdmin such that the author shows up in list mode:
class PostAdmin(admin.ModelAdmin):
list_display = [..., 'author',...]
Now in list mode, the author field will just use the __unicode__ method of the Author class to display the author. On the top of that I would like a link pointing to the url of the corresponding author in the admin site. Is that possible?
Manual method:
For the sake of completeness, I add the manual method. It would be to add a method author_link in the PostAdmin class:
def author_link(self, item):
return '%s' % (item.id, unicode(item))
author_link.allow_tags = True
That will work for that particular field but that is not what I want. I want a general method to achieve the same effect. (One of the problems is how to figure out automatically the path to an object in the django admin site.)
I was looking for a solution to the same problem and ran across this question... ended up solving it myself. The OP might not be interested anymore but this could still be useful to someone.
from functools import partial
from django.forms import MediaDefiningClass
class ModelAdminWithForeignKeyLinksMetaclass(MediaDefiningClass):
def __getattr__(cls, name):
def foreign_key_link(instance, field):
target = getattr(instance, field)
return u'%s' % (
target._meta.app_label, target._meta.module_name, target.id, unicode(target))
if name[:8] == 'link_to_':
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
setattr(cls, name, method)
return getattr(cls, name)
raise AttributeError
class Book(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
class BookAdmin(admin.ModelAdmin):
__metaclass__ = ModelAdminWithForeignKeyLinksMetaclass
list_display = ('title', 'link_to_author')
Replace 'partial' with Django's 'curry' if not using python >= 2.5.
I don't think there is a mechanism to do what you want automatically out of the box.
But as far as determining the path to an admin edit page based on the id of an object, all you need are two pieces of information:
a) self.model._meta.app_label
b) self.model._meta.module_name
Then, for instance, to go to the edit page for that model you would do:
'../%s_%s_change/%d' % (self.model._meta.app_label, self.model._meta.module_name, item.id)
Take a look at django.contrib.admin.options.ModelAdmin.get_urls to see how they do it.
I suppose you could have a callable that takes a model name and an id, creates a model of the specified type just to get the label and name (no need to hit the database) and generates the URL a above.
But are you sure you can't get by using inlines? It would make for a better user interface to have all the related components in one page...
Edit:
Inlines (linked to docs) allow an admin interface to display a parent-child relationship in one page instead of breaking it into two.
In the Post/Author example you provided, using inlines would mean that the page for editing Posts would also display an inline form for adding/editing/removing Authors. Much more natural to the end user.
What you can do in your admin list view is create a callable in the Post model that will render a comma separated list of Authors. So you will have your Post list view showing the proper Authors, and you edit the Authors associated to a Post directly in the Post admin interface.
See https://docs.djangoproject.com/en/stable/ref/contrib/admin/#admin-reverse-urls
Example:
from django.utils.html import format_html
def get_admin_change_link(app_label, model_name, obj_id, name):
url = reverse('admin:%s_%s_change' % (app_label, model_name),
args=(obj_id,))
return format_html('%s' % (
url, unicode(name)
))