Django DRF Foreign Key - angularjs

This question or many like it has been asked multiple times but for some reason I am unable to find the answer.
I do have this working to an extent in the way that if you go on the api pages, it renders, creates and updates without problem. The issue is displaying a field (title) from the nested object instead of just the primary key on the front end.
Some background before getting into the code:
Races is a finite list (e.g. Race1, Race2, Race3) and the front end does not have the ability to add more.
Cards is not finite, but each card must link to an existing Race (this currently does so by Primary Key).
The front end should display the card_text and race title of the linked race.
It also has the ability to add a new card but this works fine.
I have had this working with separate serializers for read and create/update where the read has a 'depth = 1' to pull through the entire object but the create/update doesn't and you then parse the object and send the primary key back (I couldn't find a way of doing this in the serializer, is it possible?).
So basically my question is, are you meant to pass the entire object through and parse it on a POST method, or do you pass the primary key and pull in the linked objects (Races) and use the primary key as an index (e.g. Races[card_race]). Also, why is 'linked_race' not coming through to the front end?
I realise I've almost answered my own question but as I'm new to Django I'm looking for the correct conventions and who knows, it may save someone else time when searching for the same answer.
urls.py
from .api import CardViewSet, RaceViewSet
from rest_framework.routers import DefaultRouter
from django.conf.urls import url, include
from .views import landing
router = DefaultRouter()
router.register(r'cards', CardViewSet)
router.register(r'races', RaceViewSet)
urlpatterns = [
url(r'^$', landing),
url(r'^api/', include(router.urls)),
]
api.py
from rest_framework.viewsets import ModelViewSet
from .serializers import CardSerializer, RaceSerializer
from .models import Card, Race
class CardViewSet(ModelViewSet):
queryset = Card.objects.filter(active=True)
def get_serializer_class(self):
return CardSerializer
def perform_create(self, serializer):
serializer.save(creator=self.request.user)
class RaceViewSet(ModelViewSet):
queryset = Race.objects.filter(active=True)
serializer_class = RaceSerializer
models.py
from django.db import models
from django.conf import settings
User = settings.AUTH_USER_MODEL
class Race(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=30, blank=False)
active = models.BooleanField(default=True)
def __str__(self):
return "{}".format(self.title)
def __unicode__(self):
return self.title
class Card(models.Model):
card_text = models.CharField(max_length=100, blank=False)
card_description = models.CharField(max_length=100, blank=True)
card_race = models.ForeignKey(Race, related_name='linked_race', on_delete=models.CASCADE)
creator = models.ForeignKey('auth.User', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
def __str__(self):
return self.card_text
class Meta:
ordering = ('created',)
serializers.py
from rest_framework import serializers
from .models import Card, Race
class RaceSerializer(serializers.ModelSerializer):
class Meta:
model = Race
fields = '__all__'
class CardSerializer(serializers.ModelSerializer):
linked_race = RaceSerializer(read_only=True, many=True)
class Meta:
model = Card
fields = 'id', 'card_text', 'card_description', 'card_race', 'linked_race',
Javascript extract (AngularJS)
$http.get('/api/races/').then(function (response) {
$scope.races = response.data;
$scope.selectedOption = $scope.races[0];
});
$scope.cards = [];
$http.get('/api/cards/').then(function (response) {
$scope.cards = orderBy(response.data, 'created', true);
});
html extract (AngularJS)
<div class="races--row" ng-repeat="c in cards | filter : card_filter |
orderBy : sortVal : sortDir" ng-class-odd="'odd'" ng-click="openModal(c)">
<div class="races--cell race">{{ c.card_race.title }}</div>
<div class="races--cell card-text">{{ c.card_text }}</div>
</div>

Your first "problem" is with the Card model (I say problem because I don't think you intended to do this). You're defining related_name='linked_race' for the card_race field. This related_name is the name you use to refer to a card FROM a race.
I would suggest you leave it out and use the default that Django already gives us (i.e. my_race.card_set.all() in this case). So change change that field in the Card model to:
class Card(models.Model):
...
card_race = models.ForeignKey(Race, on_delete=models.CASCADE)
...
And let's change the card serializer to:
class CardSerializer(serializers.ModelSerializer):
# no more linked_race
class Meta:
model = Card
fields = ('id', 'card_text', 'card_description', 'card_race')
Alright, this is a vary basic model serializer and you won't see details of a race yet. So now let's get to your main problem which was that you wanted to:
see the details of the associated race of a card
perform create/get/update/delete operations using the same serializer
For this, let's further change the CardSerializer to include another field called race_detail:
class CardSerializer(serializers.ModelSerializer):
race_detail = RaceSerializer(source='card_race', read_only=True)
class Meta:
model = Card
fields = ('id', 'card_text', 'card_description', 'card_race', 'race_detail')
We have defined two serializer fields for the same model field. Note the source and read_only attributes. This makes this field available when you GET a card (which is what we want), but not when you're performing POSTs or PUTs (which avoids the problem of sending the whole race object and parsing and stuff). You can just send the race id for the card_race field and it should work.

Related

How to add different many-to-many field into a single instance or row of a foreign key in django-rest-framework using model serializer

I am creating an ecommerce website using Django-rest-framework and react. I am trying to add items to cart. I am able to add items to the cart on the frontend but I want to store the cart data to the backend (Django) database so that whenever the user add items to cart and reloads the page the items should still be in his cart like any other ecommerce site. Here is my Code for django models, serializers, viewset.
class Products(models.Model):
title = models.CharField(max_length=100)
image = models.URLField()
description = models.TextField(max_length=500, blank=True, null=True)
category = models.CharField(max_length=50, blank=True, null=True)
rating = models.IntegerField(blank=True, null=True)
price = models.FloatField()
def __str__(self):
return f"{self.id}"
class Cart(models.Model):
product = models.ManyToManyField(
Products, related_name="cart")
buyer = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="cart")
def __str__(self) -> str:
return f"{self.buyer}"
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = '__all__'
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = '__all__'
class ProductsViewSet(viewsets.ModelViewSet):
queryset = Products.objects.all()
serializer_class = ProductsSerializer
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
router = routers.DefaultRouter()
router.register('products', ProductsViewSet, 'products')
router.register('cart', CartViewSet, 'cart')
I am using postman to post the cart items. I am able to add more than one products for a single buyer.
but the problem is when i again add another product to the same user using postman i added before, it creates another row for the same user.
I do not want that. I want a single instance or row of a user in cart table and add as many products as i want. when i post other products for the same user, that product should also get added up in the single user row or instance. What is the best way to achieve my goal.
Here is the issue, Django can't automatically do that because it doesn't know which behaviour is expected. If you look at the code to add a product and look at the code to add a cart, it's exactly the same. So behaviour will also be the same.
For the behaviour that you want, you will have to override the create method of your ModelViewSet.
Here are the steps to achieve what you want -
Check whether or not the user with that id already has a cart.
If they have a cart, then you'll need to fetch the cart object belonging to that user and add products to it.
If they don't, then you'll have to create a new cart object and do the default thing.
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
def create(self, request):
# Checking whether the Cart object already exists for the buyer
cart = Cart.objects.filter(buyer_id = request.data.get("buyer"))
if len(cart)=1:
#If cart exists, we loop through the list of product ids and add them
for product_id in request.data.get("product"):
cart[0].product.add(get_object_or_404(Product, id = product_id ))
if len(cart)=0:
# Doing what normally happens.
return super().create(request)
if len(cart)>1:
#Error in database. One person having multiple carts. Custom error message.
To check out how to add data to many-to-many fields, check this out.

How to give user option to select a wagtail collection of images in page?

I am looking for a way to show a list of wagtail collection as a field in a page (just like it showing when you upload an image). A user can select a collection and I can programmatically filter the images to the selected collection. I am still new to wagtail and I am not sure how should I implement this in code.
Thank you in advance for your help.
So there's a couple ways you can do this. The first, and probably the least-ideal way is to register Collection as a snippet and use a SnippetChooserPanel.
"""Register Collection snippet."""
from wagtail.snippets.models import register_snippet
from wagtail.core.models import Collection
# Register Collections as Snippets so we can use the SnippetChooserPanel to select a collection
register_snippet(Collection)
And then in your model you can use a SnippetChooserPanel, like so (note, this is all untested code)
from django.db import models
from wagtail.core.models import Page
class CustomPage(Page):
# ...
collection = models.ForeignKey(
'wagtailcore.Collection',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
content_panels = Page.content_panels + [
# ...
SnippetChooserPanel('collection'),
]
#gasman's comment on the answer has a link to another solution that's much more elegant than mine.
I've managed to do this using wagtail-generic-chooser just following the instructions on the README.md, and using wagtail core Collection model instead of People.
Aug 2022 - Wagtail 2.15.5 - display Wagtail hierarchical collection
from wagtail.admin.templatetags.wagtailadmin_tags import format_collection
class Meeting(models.Model):
COLLECTION_CHOICES = []
for c in Collection.objects.all():
COLLECTION_CHOICES.append((c.id, format_collection(c)))
title = models.CharField(max_length=100)
collection = models.ForeignKey(Collection, on_delete=models.PROTECT, help_text="Choose the 'Collection' folder for the meeting's related documents", choices=COLLECTION_CHOICES)
Edit: If you add a new collection to collections and go back the this Meeting model the new collection will not be in the list. As the COLLECTION_CHOICES is only created once for optimization. If you want a dynamic collection choice you need to make a custom form on top of your model e.g.
from wagtail.admin.forms import WagtailAdminModelForm
class MeetingAdminForm(WagtailAdminModelForm):
# This below field will be automatically added to the Meeting panel fields
meeting_collection = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(MeetingAdminForm, self).__init__(*args, **kwargs)
self.fields['meeting_collection'] = forms.ChoiceField(
initial=self.instance.collection_id,
choices=[(c.id, format_collection(c)) for c in Collection.objects.all()]
)
def save(self, commit=True):
instance = super().save(commit=False)
instance.collection_id = self.cleaned_data['meeting_collection']
if commit:
instance.save()
return instance
class Meeting(models.Model):
base_form_class = MeetingAdminForm
class Meta:
""" Meta options """
ordering = ['title']
title = models.CharField(max_length=100)
meeting_datetime = models.DateTimeField()
location = models.TextField(null=True)
collection = models.ForeignKey(Collection, on_delete=models.PROTECT, help_text="Choose the 'Collection' folder for the meeting's agenda, minutes and related documents")
committee = models.ForeignKey(Committee, on_delete=models.CASCADE)
panels = [
FieldPanel('title'),
FieldPanel('meeting_datetime'),
FieldPanel('location'),
FieldPanel('meeting_collection'),
FieldPanel('committee'),
]

file field doesn't gets saved with wagtail inline field

I'm trying to user inline panels but for plain Django model in wagtail like it's described here. I got it working with a plain char field.
When I try to use a models.FileField I get an error message after the save action. "No file chosen". It seem it doesn't saves the file
Here is the code I used:
from django.db import models
from modelcluster.models import ClusterableModel
from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel
from wagtail.wagtailsnippets.models import register_snippet
from modelcluster.fields import ParentalKey
class Slide(models.Model):
file = models.FileField('PDF / Image',null=True, upload_to="slides")
mymodel = ParentalKey('mymodel.mymodel', related_name='slides',
on_delete=models.CASCADE, null=True)
#register_snippet
class MyModel(ClusterableModel):
name = models.CharField(max_length=255)
number = models.IntegerField()
panels = [
FieldPanel('name'),
FieldPanel('number'),
InlinePanel('slides', label="slides"),
]
def __str__(self):
return self.name
How to get the files saved? Should it be possible?
This is a known bug that will be fixed in the next release of Wagtail (due in a couple of weeks): https://github.com/wagtail/wagtail/issues/2251

URL with dot in Django Rest Framework

I have the following model with a primary_key=True specified:
class Team(models.Model):
name = models.CharField(
max_length=64,
primary_key=True,
)
... other fields
When I serialize this model, I do the following:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('url', 'name',) # and another fields
My viewset:
class TeamViewSet(viewsets.ModelViewSet):
lookup_value_regex = '[-\w.]'
queryset = Team.objects.all()
serializer_class = TeamSerializer
filter_fields = ('name',) # and another fields
My urls.py:
router = routers.DefaultRouter()
router.register(r'teams', TeamViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
# I am not sure if this url is right. I repeat of include(router.urls)
url(r'^api/teams/(?P<name>[-\w.]+)/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Then, when I create a Team object with name attribute containing dot ., for example Latinos F.C. and I go to the rest url, I get:
I am not sure about of how to use the lookup_value_regex attribute in my viewset. In this answer is used with some basic regex, but if I use it, any Team object is reachable via my serialized Rest API.
How to can I get a url like as: /api/teams/Name.F.C. in my serialized Team model?
First of all check if you have set APPEND_SLASH to True in your settings, because if not - the missing slash (at the end of the URL) is a problem.
Second - I do not think that dot is a problem, the problem can be a space - coded as %20;
Third - such urls looks just ugly :) You should consider changing it to some kind of a slugs: Latinos F.C. -> latinos-fc;
If you do that (just add additional field on the model with slug - this field should be obviously unique) - set up the lookup_field on your view - this will solve your problem.
Consider the example:
views.py
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeSerializer
lookup_field = 'slug_name'
serializers.py
class SomeSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ('id', 'name', 'slug_name')
read_only_fields = ('slug_name',)
def to_internal_value(self, data):
ret = super(SomeSerializer, self).to_internal_value(data)
ret['slug_name'] = slugify(ret['name'])
return ret
models.py
class SomeModel(models.Model):
name = models.CharField(max_length=100)
slug_name = models.SlugField(unique=True, max_length=100)
urls.py
router.register(r'teams', SomeViewSet, base_name='teams')
urlpatterns = router.urls
And now:
creation:
details:
Can you do that this way? Or you really need the dots?

Django m2m 'through' with a generic foreignkey

I have the follow code example, which is a simplified abstraction of a real world project I'm working on:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class FeatureSet(models.Model):
"""
Feature Set
"""
name = models.CharField(max_length=255)
def __unicode__(self):
return u"%s" % self.name
class GenericObjectAlpha(models.Model):
title = models.CharField(max_length=255)
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
def __unicode__(self):
return u"%s" % self.title
class GenericObjectBeta(models.Model):
title = models.CharField(max_length=255)
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
def __unicode__(self):
return u"%s" % self.title
class Feature(models.Model):
"""
Feature
"""
# FK to feature set
feature_set = models.ForeignKey(FeatureSet)
# FK to generic object, Generic object alpha or beta... or others
content_type = models.ForeignKey(
ContentType,
default='article',
limit_choices_to={ 'model__in': ('genericobjectalpha', 'genericobjectbeta') },
related_name="play__feature_set__feature")
object_id = models.PositiveIntegerField(
"Feature object lookup")
content_object = generic.GenericForeignKey(
'content_type',
'object_id')
# Extra fields on a m2m relationship
active = models.BooleanField()
order = models.PositiveIntegerField()
def __unicode__(self):
return u"%s::%s" % (self.feature_set, self.content_object)
This line causes an error:
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
Obviously because the 'through' model lacks a corresponding FK to each side of the m2m. What I'd like to achieve here, is that one side of the m2m relationship is generic, and, that I can specify my own intermediary join table, to do the usual adding of custom fields etc.
What are my options for accomplishing this?
Note, its currently an important requirement to include the feature_sets = models.ManyToManyField(FeatureSet, through='Feature') line in the generic model, mostly for admin UI purposes. The reason why its generic is that its not yet determined how many models this line will be placed upon.

Resources