I am in the early stages of a project using Django, Django REST Framework, and SQL. I am very new to DRF.
I have a model that tracks user info for a game service that runs different servers for regions of the world (ex. NA, EU, etc). User IDs are only unique per-region, but the users are all stored using the same model (table). I am employing unique_together = ('user_id', 'region') in my model's Meta class to ensure there are no duplicates. Please note that, as such, the PKs in the DB are not related to the user IDs.
DRF, by default, would create endpoints using the DB's PKs of Users, but I have changed that to use a system like /users/na/123 to get the object where user_id = 123 and region = 'na' (north america). A snippet for this from urls.py follows:
url(r'^users/(?P<region>.+)/$', UserList.as_view()),
url(r'^users/(?P<region>.+)/(?P<user_id>.+$)', UserDetail.as_view()),
These are generic views (generics.ListAPIView and generics.RetrieveAPIView), respectfully.
Currently, the rest of my views are ViewSets.
One of the things I model is historical match data, where users are related to by a Game model, to keep track of who participated in a match like so:
class Game(models.Model):
player_1 = models.ForeignKey(User)
player_2 = models.ForeignKey(User)
I plan on implementing a route for games like I did w/users (again, game_id is unique only per-region) so I can do /game/<region>/<game_id>.
My question is this:
How can I get hyperlinks to Users using my established /user/<region>/<user_id> routes in Game list/detail views on the API?
Presently, my GameSerializer is defined as follows:
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
exclude = ('id',)
When I change it to a HyperlinkedModelSerializer I get the following error upon visiting the Game endpoints:
Could not resolve URL for hyperlinked relationship using view name
"user-detail". You may have failed to include the related model in
your API, or incorrectly configured the lookup_field attribute on
this field.
I assume this is because my User endpoints are implemented differently than what it expects (it can't know I have abandoned the default PK indexing method and opted for a custom route a la /users/<region>/<user_id> instead of /users/<pk>, right?)
How do I approach this problem? I would be open to suggestions that are extraneous to the DRF side of things, like restructuring my DB/Django models, if it seems like the direction I want things to go is crazy (not wanting to use PKs).
After a few more days of reading and thinking about the problem differently, it looks Meta.unique_together is kind of like expressing a composite key in SQL. This lead me to this solution:
https://groups.google.com/forum/#!topic/django-rest-framework/tHmEAzSNgG4
e.g. instead of using an URL like this to identify an employee:
api/1.3/employee/5/
I use an URL like this:
api/1.3/company/23/employee/5/
I use a HyperlinkedModelSerializer to serialise this model. I
couldn't find a way of configuring a HyperlinkedIdentityField to
handle the composite key (you can only specify a single lookup_field)
so I override the url with a SerializerMethodField instead, like this:
class EmployeeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.SerializerMethodField('get_employee_detail_url')
def get_employee_detail_url(self, obj):
# generate the URL for the composite key
...
return composite_key_url
Still exploring my options, but this looks pretty clean.
I just came up against the same problem today. After going through the Django-Rest-Framework documentation on Generic views I came across:
lookup_field - The model field that should be used to for performing object lookup of individual model instances. Defaults to 'pk'. Note that when using hyperlinked APIs you'll need to ensure that both the API views and the serializer classes set the lookup fields if you need to use a custom value.
http://www.django-rest-framework.org/api-guide/generic-views
In my case I did this in my models.py
class UserDetailView(generics.RetrieveAPIView):
model = User
serializer_class = UserSerializer
lookup_field = "username"
...and works lovely now. Hope that helps.
Related
Some days ago a guy explained me that on ruby on rails the queries are done on models. Because it gets already saved at your data before be requested on views and the query.
By the way I've learned and had been working until now, I'm setting the query on views.py and passing, through a context variable. So I started to read about Model.Manager and still didn't find a answer to which way is better:
queries made on views
queries made by simple functions on models
queries made on models.Manager class for each model
Use custom QuerySets and ModelManagers in your models
call your model's ModelManager custom methods from within your views
pass the returned values (querysets, instances or whatever) to the templates (or JSON serializer etc)
It's a matter of separation of concerns:
the template (or json serializer or whatever) doesn't have to know where data come from nor they were obtained - only what the data structure is
views don't have to know about how the query is implemented, only on how to get relevant data
only the model layer should know about it's implementation (encapsulation 101)
Every model is associated with a Manager (default one is objects)
>>> from django.contrib.auth.models import User
>>> user = User.objects.all()
>>> type(user)
<class 'django.db.models.query.QuerySet'>
When you need to make any queries to the model you need a manager for this. In the above example user doing a query on objects manger of User Model.
3-queries made on models.Manager class for each model - correct interpretation
Click here for Documentation
Ref.
By default, Django adds a Manager with the name objects to every Django model class.
If u have specific business logic, you can make use of managers to override built-in model methods like save() and delete() to add business logic to default database behaviour or you can specifically design some query logic.
file name ---- > models.py
from .managers import ModelNameManager
class ModelName(Base):
title = models.CharField(max_length=255, blank=True, null=True)
headline = models.CharField(max_length=255, blank=True, null=True)
objects = ModelNameManager()
create a file managers.py file in your application
file name ---- > managers.py
class ModelNameQuerySet(models.QuerySet):
def by_name(self, id):
return self.filter(id=id)
class ModelNameManager(models.Manager):
def get_queryset(self):
return ModelNameQuerySet(self.model, using=self._db)
def by_name(self, ad):
return self.get_queryset().by_name(id)
in Views.py or any services file file your query will be
import ModelName
obj = ModelName.get_queryset(id)
obj.title
this will return the object based on the query written in managers.
I hope this is helpful.
According to the Django idioms want to add some advice that can be useful:
model managers it's a place for most common queries, not for all queries. Managers describe basic methods for working with models. If you have a widely-used logic for your model with queryset - put it to the manager's method. For example, my users split by domains, so I want to get the user by name and domain, so add the get_by_name_and_domain method to the user model manager. Also, you can access your model by the model attribute in the manager.
models are the part of MTV (Model-View-Template) model on Django and the main purpose of the 'model' itself is to describe db-object but not related business logic. So put your custom queries to views and remember about DRY principle.
Say we're building a Django-based site that clones Medium.com's URL structure, where you have users and articles. We'd probably have this model:
class Article(models.Model):
user = models.ForeignKey(User)
slug = models.CharField()
We want to be able to build URLs that look like /<username>/<slug>/. Since we're going to have billions of articles and zillions of pageviews, we want to put an index on that model:
class Meta:
indexes = [
models.Index(fields=['user__username', 'slug'])
]
But this causes the makemigrations command to fail with the following error:
django.core.exceptions.FieldDoesNotExist: Article has no field named 'user__username'. The app cache isn't ready yet, so if this is an auto-created related field, it won't be available yet.
So, plain vanilla models.Index doesn't support relational lookups like a QuerySet does. How would I add an index like this? Let's assume PostgreSQL, if that's helpful.
It seems that you can't make multi-table index according to this answer.
So if it's not possible in the database, I don't see how can Django offer this feature...
What you can do to make your queries more efficients is an index using user_id and slug.
Django index meta class mainly provide declarative options for indexing table fields,
you can create an index using several field of a model or create different index for every fields of the model. you just don't have to provide user foriegnkey field name attribute which generate automatic user_id index migrations
migrations.AddIndex(
model_name='candidates',
index=models.Index(fields=['user'], name='candidates__user_id_569874_idx'),
),
you can also set the index name in the model meta, and db_tablspace as well if needed.
I'm really really confused about how django handles database relationships.
Originally I had an article model that contained a simple IntegerField for article_views, recently I'm trying to expand the definition of a article_view to contain it's own fields so I created a model for it. (IP, SESSION KEY etc..)
I'm at a bit of a loss regarding how to make the relationship, to me it makes the most sense to have a one-to-many field inside the article model, because an article can have many different views, but a view can only be part of one article.
all the implementations I'm seeing have this set up in a really weird reverse manner, what gives?
Unfortunately Django does not have a One-to-Many field. This is achieved by creating a ForeignKey on in this case the ArticleView model. When you want to easily access the article views in your template you can set the related_name on the ForeignKey.
class Article(models.Model):
# Article definition
class ArticleView(models.Model):
article = models.ForeignKey(Article, related_name='views')
In the template you can now use article.views.count() to get the number of views coupled to an account.
Please note that this creates a database query for each count you want. It would probably be better to have a queryset with annotate: Article.objects.annotate(num_views=Count('views'))
I want to use two different models for django.contrib.auth module. The first one is the default User model provided by Django which is completely suitable for admin access (groups, permissions etc.) but the other one is customer model which has a lot of different attributes (city, locale, address etc.) compared to default User model. These user groups must use different tables and mustn't have any relation.
I created a Customer model inherited from AbstractBaseUser and a middleware class called ChangeBaseUser like this:
class ChangeBaseUser(object):
def process_request(self, request):
match = resolve(request.path)
if match.app_name == "myapp":
settings.AUTH_USER_MODEL = 'myapp.Customer'
else:
settings.AUTH_USER_MODEL = 'auth.User'
It's working but I'm not sure whether this is the proper way to do it because in documentation there is a section (link) that implies the convenient way is to assign a static value for default user model.
If this is not the proper way, do you have any suggestions for having multiple user models per module basis?
If your requirement is to keep admin users and customers separate, I don't see anything wrong with having multiple user models. At this point, the customer model is like any model, except it is very similar to the user model and that is perfectly fine if that works for you. The only disadvantage is that you will have to possibly duplicate many helpers django gives you for the Django user model such as auth backend or sessions for users. If you are willing to do all that, this seems perfectly fine.
If you wish however to utilize many of the django helpers you might want to create a very basic user model which will serve as a base for both admins and customers:
class User(AbstractBaseUser):
# use this for auth and sessions
class Admin(models.Model):
user = models.OneToOneField(UserBase, related_name='admins')
# ... other admin-specific fields
class Customer(models.Model):
user = models.OneToOneField(UserBase, related_name='admins')
# ... other customer-specific fields
This will allow you to reuse many of the things Django provides out of the box however it will incur some additional db overhead since more joins will have to be calculated. But then you can cache things for customers so you can get some of the performance back.
I have these models:
class Company(models.Model):
name=models.CharField(max_length=100)
description=models.TextField()
#some more fields
class Product(models.Model):
name=models.CharField(max_length=100)
company=models.ForeignKey(Company)
#some more fields
class Category(models.Model):
parent=models.ForeignKey('self',null=True,blank=True)
name=models.CharField(max_length=100)
products=models.ManyToManyField(Product,null=True,blank=True)
#some more fields
as U can see each company has a list of product and each product belongs to some categories,I'm going to get the list of categories of each company using company pk,what's the best practice?should I define a database view?how can I do this?
Note:I've not ever used database view in django,I searched about it and that doesn't sound easy to me!
I always try to avoid using database views, stored procedures and in general stuff that 'lives' in the database itself rather than in the application code-base for the simple reason that it is very hard to maintain (and also you say good bye to database agnostic applications).
My advice here is to stick with django orm (which can do a lot) and only if you unable to get decent performances or if you need some advanced feature available through stored procedures/views only then to go for that solution.
Using views in django is quite easy.
Say you have 1 view to query, you create the view on the db then you write the model with fields matching the view' columns (name and type).
UPDATE:
You then need to set the table name as the view name in meta class definition.
After that you need to tell django not to write on that and to not try to create a table for the view model, luckily there is a conf for that:
class ViewModel(models.Model):
... view columns ...
class Meta():
db_table = 'view_name'
managed = False
I've no idea why you think you need a db view here. Generally, you don't use them with Django, since you do all the logic in Python via the ORM.
To get the list of categories for a company, you can just do:
categories = Category.objects.filter(products__company=my_company)
where my_company is the Company instance you're interested in.