Django Rest Framework, POSTable Foreign Data - django

Is it possible to Post Foreign Data in Django Rest Framework rather than the "ID" of the data? I was looking into HyperlinkedModelSerializer, however I felt that there wasn't enough documentation on the DRF website. I have two models. Showtime has a link to Cinema.
class Cinema(models.Model):
name = models.TextField("Name",)
slug = models.SlugField("Slug",blank=True)
phone = models.TextField("Phone")
phone2 = models.TextField("Secondary Phone", blank=True, null=True)
email = models.EmailField("Email")
website=models.TextField("Website")
location = models.TextField("Location")
def __unicode__(self):
name = (self.name).title()
return name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Cinema, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('Cinema:detail', kwargs = {'slug': self.slug })
class Showtime(models.Model):
cinema = models.ForeignKey(Cinema)
showDate = models.TextField("Date",)
showTime = models.TextField("Time",)
slug = models.SlugField("Slug", blank=True, unique=True)
def save(self, *args, **kwargs):
self.slug = uuid.uuid4()
super(Showtime, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('Showtime:detail', kwargs = {'slug': self.slug})
Each model have been serialized using DRF. Is is possible to post Cinema Data via the Showtime API View rather than have to post cinema in Cinema API View first then get ID of that field then post via the Showtime View to link the data?
Basically, All I want is full writable Foreign Key support, or Does DRF support something like get_or_create() ?

You can nest serializers. See the docs at http://www.django-rest-framework.org/api-guide/serializers#dealing-with-nested-objects for an example.

Related

Django get objects created by users, with those users belonging to a list of users

I have a model for followers. Here is the model:
class Follow(models.Model):
followee = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followee")
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follower")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
unique_together = ["followee", "follower"]
def __str__(self):
return "{} is following {}".format(self.follower, self.followee)
def save(self, *args, **kwargs):
if self.followee == self.follower:
return "You cannot follow yourself"
else:
super().save(*args, **kwargs)
Users create multiple types of objects like posts and questions. I would like to show all the posts and questions of all the users that follow a specific user. Simplified show me all the posts and questions of users that follow me.
I am using a module called drf_multiple_model and here is my view, which I cannot get to work. It gives me the following error that I don't understand:
Cannot use QuerySet for "Follow": Use a QuerySet for "User".
Here is the view I am using:
def get_followers(queryset, request, *args, **kwargs):
id = kwargs['user']
user = User.objects.get(id=id)
followers = Follow.objects.all().filter(followee=user)
return queryset.filter(user__in=followers)
class HomeView(FlatMultipleModelAPIView):
permission_classes = [IsAuthenticated]
def get_querylist(self):
querylist = [
{'queryset':Post.objects.all(), 'serializer_class': UserPostSerializer, 'filter_fn': get_followers, 'label':'post'},
{'queryset':Question.objects.all(), 'serializer_class': QuestionSerializer, 'filter_fn': get_followers, 'label':'question'},
]
return querylist
What am I doing wrong please?
In order to be able to use the __in filter the followers should be an iterable of Users. Try this:
followers = [f.follower for f in Follow.objects.filter(followee=user)]
or
followers = [f.follower for f in user.follower.all()]
You can filter with a JOIN, like:
def get_followers(queryset, request, *args, **kwargs):
return queryset.filter(user__follower__followee_id=kwargs['user'])
This will fetch the items in a single query, and thus avoid first querying the followers and then obtain the items.

Auto generate and save data to SlugField

Hello I have a function to auto generate data for my SlugField but i dont know how to implement a save method to execute it. Ive tried calling the function from the save method but it raises an error of 'instance not defined'. Any suggestions will help. Thanks.
def ran_str_gen(size=6, chars=string.ascii_letters + string.digits):
return ''.join(secrets.choice(chars) for s in range(size))
def slug_gen(instance, new_slug=None):
if new_slug is not None:
slug=new_slug
else:
slug = slugify(instance.title)
op = instance.__class__
qs_exists = op.objects.filter(slug=slug).exists()
if not qs_exists:
new_slug = '{slug}-{ranstr}'.format(slug=slug, ranstr=ran_str_gen())
return slug_gen(instance, new_slug=new_slug)
return slug
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
slug = models.SlugField()
def save(self, *args, **kwargs):
slug_gen()
You should pass the instance (self) to the slug_gen function, store the result in the slug field, and then make a super() call to save the model effectively, so:
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slug_gen(self)
super().save(*args, **kwargs)
Note: You can make use of django-autoslugĀ [GitHub] to automatically create a slug based on other field(s).
Note: Normally you should not change slugs when the related fields change. As is written in the article Cool URIs don't changeĀ [w3.org], URIs are not supposed to change, since these can be bookmarked. Therefore the slug should only be created when creating the object, not when you change any field on which the slug depends.
def save(self, *args, **kwargs):
self.slug=slug_gen()

Django models.py (API result) - retrieve current post to add api result

I'm new to Django I got an issue. I don't know how to retrieve the current post inside of models.py. I've tried different way for that.
'QuerySet' object has no attribute 'aliments'
or no error and no add to Post from ListAliments
get_object_or_404(Post, id=kwargs['id'])
here is my models.py
class ListAliments(models.Model):
name = models.CharField(max_length=40, unique=True)
slug = models.SlugField(editable=False)
status = models.IntegerField(choices=STATUS, default=1)
def save(self, *args,**kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.name))
super(ListAliments, self).save(*args, **kwargs)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=190)
url_image = models.URLField(max_length=200, default=None)
aliments = models.ManyToManyField('ListAliments',blank=True, related_name='listaliments_post')
...
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.title))
super(Post, self).save(*args, **kwargs) -> First save for Post which has not no ID
...
if self.url_image:
request = ...
response = ...
if response:
names = []
for concept in response.outputs[0].data.concepts:
current_aliments = ListAliments.objects.filter(name=concept.name)
current_post = Post.objects.filter(url_image=self.url_image) #get_object_or_404(Post, id=kwargs['id'])
if current_aliments.count()<1:
create_aliments = self.aliments.create(name=concept.name)
current_post.aliments.add(create_aliments)
else:
existed_aliments = ListAliments.objects.get(name=concept.name)
current_post.aliments.add(existed_aliments)
super().save(*args, **kwargs)
Post.objects.filter(url_image=self.url_image) returns QuerySet
in order to get object call first so Post.objects.filter(url_image=self.url_image).first(); note that you can get None

Django Rest Framework, "lookup_field" value that is defined on models level

I faced with an issue in Django Rest Framework when I try to implement hyperlink related stuff.
Let's say there is a model with slug that is defined during save procedure
class Family(models.Model):
name = models.CharField(unique=True, max_length=150)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.slug)
super().save(*args, **kwargs)
So in my serializers package I have the following:
class FamilySerializer(serializers.ModelSerializer):
class Meta:
model = Family
fields = ["name", "slug", "description", "url"]
extra_kwargs = {
"url": {"view_name": "api:family-detail", "lookup_field": "slug"},
# I have to mark this field as not required one to be able POST wo this field
"slug": {"required": False}
}
When I try to create a new Family through POST request, of course, I'm getting
django.urls.exceptions.NoReverseMatch: Reverse for 'family-detail' with keyword arguments '{'slug': ''}' not found. 2 pattern(s) tried: ['api/families/(?P<slug>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$', 'api/families/(?P<slug>[^/.]+)/$'] and django.core.exceptions.ImproperlyConfigured:... exception as well.
And here is my ViewSet:
class FamilyViewSet(ModelViewSet):
"""
Provides CRUD operations on Family entity.
"""
serializer_class = FamilySerializer
queryset = Family.objects.all()
lookup_field = "slug"
permission_classes = [permissions.IsAdminUser]
How to elaborate the issue I faced. Should I modify FamilyViewSet to generate slug on ViewSet level or take a look at "custom" reverse?
My fault/typo in save method:
def save(self, *args, **kwargs):
self.slug = self.name(self.slug) # <-- must be self.name, but not self.slug
super().save(*args, **kwargs)

Auto create related model

I am wondering if it's possible to auto create a related model upon creation of the first model.
This is the models
class Team(models.Model):
name = models.CharField(max_length=55)
class TeamMember(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE, null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
So what I want to do is something like this on the 'Team' model
class Team(models.Model):
name = models.CharField(max_length=55)
#on_new.do_this
TeamMember.team = self
TeamMember.user = request.user
TeamMember.save()
I have tried to find any documentation about this. But only found some example about onetoonefields. But nothing about this.
Appreciate any help. Cheers!
I am assuming you are using forms to create team.
There is no direct way to create the TeamMember instance without the current user(via request). request is available in views only(unless you are using special middleware or third party library to access it), so we can send it form and create the user by overriding the save method of the modelform.
So you can try like this:
# Override the model form's save method to create related object
class TeamForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(TeamForm, self).__init__(*args, **kwargs)
class Meta:
model = Team
def save(self, **kwargs):
user = self.request.user
instance = super(TeamForm, self).save(**kwargs)
TeamUser.objects.create(team=instance, user=user)
return instance
And use this form in View:
# Update get_form_kwargs methods in create view
class TeamCreateView(CreateView):
form_class = TeamForm
template = 'your_template.html'
def get_form_kwargs(self):
kw = super(TeamCreateView, self).get_form_kwargs()
kw['request'] = self.request
return kw
Update
(from comments)If you have the user FK availble in Team then you can use it to create TeamMember by overriding the save method. Try like this:
class Team(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL)
name = models.CharField(max_length=55)
def save(self, *args, **kwargs): # <-- Override
instance = super(Team, self).save(*args, **kwargs)
TeamMember.objects.create(user=instance.user, team=instance)
return instance