Django views/filters - django

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.

Related

DJANGO How to filter queryset with ListView?

I'm currently building a website with Django and my problem is the following : I have a page, home, which goal is to display all Plat objects in the database (Plat is a model from the database). What I want is having kind of a filtering table next to the list, which enables to filter the objects on various attributes. For example, we could filter the objects with a price greater than 10 euros, or the objects with a certain attribute lower than 5, or both at the same time.
here are the relevant parts of my files :
views.py
class home(ListView):
model = Plat
context_object_name = "plats"
template_name = "actualites/home.html"
paginate_by = 9
urls.py
urlpatterns = [
path('home', views.home.as_view(), name = 'home'),
]
The home.html file is made with bootstrap and is really big so I will not display it since I don't think it's much useful. A solution to my problem may be to put parameters in the url path('home/<int:price>', views.home.as_view(), name = 'home'), and overwrite get_queryset in the home view def get_queryset(self):
return Plat.objects.filter(prix=self.kwargs['price'])
The problem is that the filtering can be done on many attributes so my url would be really big, like home/<int:attr1_min_value>/<int:attr1_max_value>/<int:attr2_min_value>/...... Moreover, I would have to put default value for parameters for example attr2_min_value = 0 by default to enable me to filter only on attr1. But I don't think it's possible with Django. And I'm not sure about how to make the link between the filter buttons in the template and the arguments in the url.
What is the correct way to proceed?

Django rest framework: automatically create a url for each field of a model

I have large table of data (~30 Mb) that I converted into into a model in Django. Now I want to have access to that data through a REST API.
I've successfully installed the Django REST framework, but I'm looking for a way to automatically create a URL for each field in my model. My model has about 100 fields, and each field has about 100,000 entries.
If my model is named Sample,
models.py
class Sample(models.Model):
index = models.IntegerField(primary_key=True)
year = models.IntegerField(blank=True, null=True)
name = models.TextField(blank=True, null=True)
...97 more fields...
then I can access the whole model using Django REST framework like this:
urls.py
class SampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Sample
fields = ( **100 fields**)
class SampleViewSet(viewsets.ModelViewSet):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
router = routers.DefaultRouter()
router.register(r'sample', SampleViewSet)
But of course my browser can't load all of that data in a reasonable amount of time. I could manually make a different class and URL for each field, but there must be a better way... I want to be able to go to my_site.com/sample/year (for example) and have it list all of the years in JSON format, or my_site.com/sample/name and list all the names, etc.
Please help me figure out how to do this, thanks!
You might be able to do that using a custom viewset route.
You have this:
class ModelViewSet(ModelViewSet):
#list_route()
def sample_field(self, request):
desired_field = request.data.get('field', None)
if not desired_field:
return response # pseudocode
values = Model.objects.all().values_list(desired_field, flat=True)
# serialize this for returning the response
return Response(json.dumps(values)) # this is an example, you might want to do something mode involved
You will be able to get this from the url:
/api/model/sample_field/?field=foo
This extra method on the viewset will create a new endpoint under the samples endpoint. Since it's a list_route, you can reach it using /sample_field.
So following your code, it would be:
mysite.com/sample/sample_field/?field='year'
for example.
There are many interesting details in your question, but with this sample I think you might able to achieve what you want.
Try to use pagination. You can do it in almost the same way as in you question. Pagination in django lets you divide the results into pages. You don't have to display all the entries in the same page. I think this is the best option for you.
Refer django documentation on pagination:
Pagination in django

How to link two different models in Django

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

paginated result for related field in django?

Suppose I have the following models.
Class Blog:
pass
class Entry:
models.ForeignKey(Blog)
I want to do something like this
filter & sort blog by some criteria
get related entries for page 2
Can I do something better than below?
blog_ids = Blog.objects.filter(q).order_by(order)
entry_qs = Entry.objects.filter(id__in=blog_ids)
paginator = Paginator(entry_qs)
entries = paginator.page(2)
I think you need to use order_by on the entries rather than the blog object. An easier way of implmenting sorting and pagination of blog entries would be using a class based view like ListView. For eg. in your views.py:
from django.views.generic import ListView
from .models import Entry
class BlogView(ListView):
queryset = Entry.objects.order_by("-date")
paginate_by = 10
You will need to mention it in the urls.py:
url(r'^$', BlogView.as_view(), name='home'),
Now, if you use a url like /page=1 it will show the second page of entries (passed as object_list)

Foreign keys in django admin list display

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)
))