Django one-to many - python

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

Related

What is the best approach to bind multiple models to one ManyToMany field in Django?

I'm building a dashboard app that I would like the user to be able to customize. One of these customizable options would be the ability to choose different types of graphs to display data. My research into the best way to do this would be to use a ManyToMany field within my dashboard model; however, ManyToMany fields only allow 1 model--not multiple according to the docs:
A many-to-many relationship. Requires a positional argument: the class to which the model is related, which works exactly the same as it does for ForeignKey, including recursive and lazy relationships.
More research brought me to this SO post which recommended to create an intermediary 'ABCDRel' model that has foreign keys to each of the other models and emulate the behavior I'm looking for. The problem with that is I get this: as compared to what I'm looking for in this:
I use the admin page here to give a clearer picture of what I'm looking for.
Here's the intermediary I made according to the SO post above:
class GraphRelations(models.Model):
heatmap = models.ForeignKey(Heatmap, on_delete=models.CASCADE, related_name='m2m')
bar = models.ForeignKey(Bar, on_delete=models.CASCADE, related_name='m2m')
And the ManyToMany field I have in my dashboard model:
graphs = models.ManyToManyField(GraphRelations)
The only other option I can think of would be to add a foreign key to each graph model I have and set an 'enabled' field within that graph model. However, that's not very extensible and I feel there are cleaner ways to do this.
Any pointers are helpful!

How to add a conditional field in a Django model?

Basically, what I want is a field to be available if a condition is met, so something like this:
class ConditionalModel(models.Model):
product = models.ForeignKey(product, on_delete=models.CASCADE)
if category == "laptop":
cpu_model = models.CharField(max_length=200)
so if I were to go to the Django admin page and create an instance of the model and then choose "laptop" as the product from the drop-down list of existing "products", a new field would be available. I couldn't find anything about this in the documentation, so I'm wondering whether it's even possible.
What you are asking for is not "technically" possible. A model relates a database object, and under traditional SQL rules, this isn't possible. You could instead make that field optional, and then customize the admin page's functionality.
Another potential option, though I do not have much experience with it, would be to use a NoSQL database in the case where you don't want to store NULL values in your db.
I do not think it is possible because models defines databases tables so the column has to be present.
You can use the keyword blank=True to allow an object without this field.
Maybe you can customize the admin interface to hide the field in some cases.
You can't do that in models.
You can hide it in admin panel or you can make separate model for laptop.
Or you can make field blank=True
Making a field optional is not possible but you can use a generalized model called Product and two or more specialized ones called for example : ElectronicProduct that contains the field cpu_model and NonElectronicProduct, the two specialized models have to contain a OneToOneField to the Product model to ensure inheritance.

How to generate indexes for related fields on Django models?

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.

Django form to create several objects with one-to-one relation

I have several models with one-to-one relation. For example
class Task(models.Model):
initial_comment = models.OneToOneField('Comment')
# A pack of other fields
class Comment(models.Model)
body = RichTextField()
# A pack of other fields
I want to create "create view" based on form, that gives user ability to create task and initial comment there.
I can't use CreateView because it is based on only one model
I can't use ModelForm because it is based on only one model
I can create several forms, but I can't join them into one formset (forms are different)
I feel "inlineformset_factory" (InlineFormSet) should be used here, but I am not sure it suits best. Is there any 3rd party Django app to do that?
Sure I can create form myself, but I do not want to copy/paste all fields, their types, localized labels, validations and so on. I just want to list their names (like fields attibute).
I can also have 2 forms and support them everywhere and track dependencies manually (save comments first), like in How can create a model form in django with a one-to-one relation with another model , but I hope there has to be better solution.
If you wonder why do I need one-to-one: Comments are used heavily in other places and have different relations with different models.
The inline_formset factory is correct. There are no standard generic views for this, but there is a third party package with generic views to do what you are wanting. Its in the standard Django way of doing things.
https://github.com/AndrewIngram/django-extra-views
You probably want to use the CreateWithInlinesView for that.
Well, I found solution.
CreateWithInlinesView works perfectly with OneToOneField (after all, 1-to-1 is just a foreign key with constraint), but my main model here is Comment, not Task. So I should set Comment as model field in this view and Task as inline. It looks silly. I will create custom form or review my model structure.

Getting Django REST Framework's HyperlinkedModelSerializer to recognize non-default routes

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.

Categories