Django Rest framework serializer exclude foreign key with depth 2 - django

I have made an api that returns an object as json data. I Am using the django-rest-framework and its serializer. Using the resources (ModelResource) I excluded some fields, like a property called 'owner'. One of the fields is a foreignkey to itselve. I want to show this field in the api (so I use depth=2), but I want to exclude the same fields as I excluded in the object returning.
Is there a nice way to do this (I have tried several things without the wanted result).
This is my (simplified) code:
in models.py:
class MyObject(models.Model):
name = models.CharField(max_length=256, blank=True)
parent = models.ForeignKey('self', blank=True, null=True, default=None)
and_some_otherfields = models.otherFields(....)
owner = models.ForeignKey(User, null=True, blank=True, related_name='myobject_owner')
in resource.py:
class MyObjectResource(ModelResource):
model = MyObject
exclude = ('owner','and some other fields',)
and in the view used to return the object it returns this:
data = Serializer(depth=2).serialize(my_object)
return Response(status.HTTP_200_OK, data)
In the response it leaves out the exclude fields (as I wanted and expected).
but in the field parent, the parent myobject with all fields I want to hide.
I Am looking for a way to indicate that for this parent object, the serializer should use the same Resource, or add the secundary fields to the exclude list....
If I use depth =1 it only shows whether it has a parent ([]), or null if not, and i do need to know at least the parent's ID.

Ah, i just found it:
I need to add in the resource for all fields I want to show what resource....
fields = ('name', ("parent","MyObjectResource") , 'and all the other fields you want to see as well...')
I found it here: google groups forum question
You can skip the exlude, it ignores it, and just add the fields you want to show, you do not have to define them, unless you need to indicate what resource to use.
So following is the final code of the resource.py part:
class MyObjectResource(ModelResource):
model = MyObject
fields = ('name', ("parent","MyObjectResource"), 'and all the other fields you want to see as well...')

Here is how an other solution could be.
class ProToPicturesSerial(serializers.ModelSerializer):
pro_pictures = PictureSerializer(many=True)
pro_videos = VideoSerializer(many=True)
city_pro = CitySerializer(many=True)
class Meta:
model = Province
fields = ('id', 'name', 'intro', 'description', 'display_url', 'pro_pictures', 'pro_videos', 'city_pro')

Related

How to dynamically specify fields for nested serialization

When working with Django rest-framework, and object relationships, I often encounter situations in which I want to return different fields from a serializer depending on the context in which I'm calling that serializier. To provide an example, lets say I have some models representing artiste and album
class Artiste(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
songs = models.ManyToManyField(Song, related_name='artiste')
albums = models.ManyToManyField(Album, related_name='artiste')
class Album(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=30)
description = models.TextField(blank=True, null=True)
These models being serialized by these serializers, which include field from the many to many relationship
class AlbumSerializer(serializers.ModelSerializer):
artiste = ArtisteSerializer(many=True)
class Meta:
model = Album
fields = ('id','title','description','artiste')
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'albums')
A request to my album endpoint might produce an object like this
"id": "5807bd12-254f-4508-b76f-02dc0740e809",
"title": "Test",
"description": "",
"artiste": [
{
"id": "31f09ef0-50ce-48b1-96a6-a6c234930ce5",
"name": "Alec Benjamin",
"albums": [
"5807bd12-254f-4508-b76f-02dc0740e809"
]
}
]
As you can see, using nested serialization ends up giving me more information that I actually want in this context. Given that I had more data for example, when looking at the information for an artiste, I'd love to have the information regarding all the albums that the artiste has made, but when looking at my album endpoint, I'd rather not have this information be included, especially here where the only album the artiste has made is the one I'm currently looking at anyways. As the system gets larger, this problem gets worse as i include songs, playlists etc
What I want to do, is to be able to specify what serialzier fields I want depending on the context in which I'm calling that serializer, as opposed to defining my serializer once and then always getting the same information even when its not needed.
my current approach to this is to use a SerializerMethodField and adding this to my album serializer
artiste = serializers.SerializerMethodField('get_artiste')
def get_artiste(self, obj):
return [(i.id ,i.name)for i in obj.artiste.all()]
Doing this allows me to explicitly state what I want to return in the context of my album serializer but this makes my artiste field read only, meaning that when trying to create an album, I cannot specify what artiste it belongs to. This issue also seems to be the case with StringRelatedField etc.
What is your approach to doing nested serialization? Is there any way to specify what fields should be returned from serialization in a specific situation without rewriting the serializer or making the field read only?
Sorry for the lengthy, probably stupid question and thanks for your help.
according to documentation serializers classes is very like regular django form classes so you only need to specify which fields you want that serializer to show and it will be done in your example it will be :
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name')
and now this serializer will only show the related 'id' and 'name' of the given artiste model.
and there is much more that you can do here like special fields = '__all__'
which will show all fields of a given model or instead of using fields you can use exclude = ('album',) that just removes these fields from serializer .
also you can use django's models relationship here as well.
update :
to be able to use these serializers in different endpoints it is better to use python class inheritance create a base serializer and use different attributes on childs
class BaseArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'artist')
class ArtisteSerializer(BaseArtisteSerializer):
class Meta(BaseArtisteSerializer.Meta):
fields = ('id', 'name')

Django forms with odd model relationship

I am working with an existing database that I can not modify and having some trouble trying to deal with presenting forms for modifying the database in Django. The structure in question is as follows and all models are unmanaged.
class Persons(models.Model):
personid = models.BigIntegerField(primary_key=True, db_column='PersonID')
....
class Phones(models.Model):
phoneid = models.BigIntegerField(primary_key=True, db_column='PhoneID')
number = models.CharField(max_length=60, db_column='Number', blank=True)
type = models.CharField(max_length=15, db_column='Type', blank=True)
...
class Personsphones(models.Model):
personphoneid = models.BigIntegerField(primary_key=True, db_column='PersonPhoneID')
personid = models.ForeignKey(Persons, db_column='PersonID')
phoneid = models.ForeignKey(Phones, db_column='PhoneID')
...
I want to create a form to display all of the 'Phones' associated with a particular 'Persons' and in addition be able to modify/add/remove 'Phones' belonging to a 'Persons'. Right now the only thing I can think of is to display the 'Phones' in a modelformset and then if one is added or removed manually set the 'Personsphones' relation. Any ideas on how to best deal with this model setup?
For making changes to your models you may want to use django-south http://south.aeracode.org/docs/
As far as displaying your 'Phone' under your forms.py you may want to set up class meta like so. With this any changes made to models will reflect on change
class Meta:
model = Persons
exclude = ('user')
In models you may want to use Foreignkey fore relationships between phones and Persons. Better seen in action here https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey

Changed Django's primary key field, now items don't appear in the admin

I imported my (PHP) old site's database tables into Django. By default it created a bunch of primary key fields within the model (since most of them were called things like news_id instead of id).
I just renamed all the primary keys to id and removed the fields from the model. The problem then came specifically with my News model. New stuff that I add doesn't appear in the admin. When I remove the following line from my ModelAdmin, they show up:
list_display = ['headline_text', 'news_category', 'date_posted', 'is_sticky']
Specifically, it's the news_category field that causes problems. If I remove it from that list then I see my new objects. Now, when I edit those items directly (hacking the URL with the item ID) they have a valid category, likewise in the database. Here's the model definitions:
class NewsCategory(models.Model):
def __unicode__(self):
return self.cat_name
#news_category_id = models.IntegerField(primary_key=True, editable=False)
cat_name = models.CharField('Category name', max_length=75)
cat_link = models.SlugField('Category name URL slug', max_length=75, blank=True, help_text='Used in URLs, eg spb.com/news/this-is-the-url-slug/ - generated automatically by default')
class Meta:
db_table = u'news_categories'
ordering = ["cat_name"]
verbose_name_plural = "News categories"
class News(models.Model):
def __unicode__(self):
return self.headline_text
#news_id = models.IntegerField(primary_key=True, editable=False)
news_category = models.ForeignKey('NewsCategory')
writer = models.ForeignKey(Writer) # todo - automate
headline_text = models.CharField(max_length=75)
headline_link = models.SlugField('Headline URL slug', max_length=75, blank=True, help_text='Used in URLs, eg spb.com/news/this-is-the-url-slug/ - generated automatically by default')
body = models.TextField()
extra = models.TextField(blank=True)
date_posted = models.DateTimeField(auto_now_add=True)
is_sticky = models.BooleanField('Is this story featured on the homepage?', blank=True)
tags = TaggableManager(blank=True)
class Meta:
db_table = u'news'
verbose_name_plural = "News"
You can see where I've commented out the autogenerated primary key fields.
It seems like somehow Django thinks my new items don't have news_category_ids, but they definitely do. I tried editing an existing piece of news and changing the category and it worked as normal. If I run a search for one of the new items, it doesn't show up, but the bottom of the search says "1 News found", so something is going on.
Any tips gratefully received.
EDIT: here's my ModelAdmin too:
class NewsCategoryAdmin(admin.ModelAdmin):
prepopulated_fields = {"cat_link": ("cat_name",)}
list_display = ['cat_name', '_cat_count']
def _cat_count(self, obj):
return obj.news_set.count()
_cat_count.short_description = "Number of news stories"
class NewsImageInline(admin.TabularInline):
model = NewsImage
extra = 1
class NewsAdmin(admin.ModelAdmin):
prepopulated_fields = {"headline_link": ("headline_text",)}
list_display = ['headline_text', 'news_category', 'date_posted', 'is_sticky'] #breaking line
list_filter = ['news_category', 'date_posted', 'is_sticky']
search_fields = ['headline_text']
inlines = [NewsImageInline]
The answer you are looking for I think would lie in the SQL schema that you altered and not in the django models.
It could probably have something to do with null or blank values in the news_category_id, or news that belongs to a category that doesn't exist in the news_category. Things I'd check:
You have renamed the primary key on the News category from news_category_id to id. Does the foreign key on the News also map to news_category_id and not anything else?
Are all the values captured in the news.news_category also present in news_category.id
Also, as an aside, I don't see any reason why you need to rename the primary keys to id from something that they already are. Just marking them primary_key=True works just fine. Django provides you a convenient alias pk to access a model's integer primary key, irrespective of what the name of the field actually is.

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)

Django form linking 2 models by many to many field

I have two models:
class Actor(models.Model):
name = models.CharField(max_length=30, unique = True)
event = models.ManyToManyField(Event, blank=True, null=True)
class Event(models.Model):
name = models.CharField(max_length=30, unique = True)
long_description = models.TextField(blank=True, null=True)
I want to create a form that allows me to identify the link between the two models when I add a new entry. This works:
class ActorForm(forms.ModelForm):
class Meta:
model = Actor
The form includes both name and event, allowing me to create a new Actor and simultaneous link it to an existing Event.
On the flipside,
class EventForm(forms.ModelForm):
class Meta:
model = Event
This form does not include an actor association. So I am only able to create a new Event. I can't simultaneously link it to an existing Actor.
I tried to create an inline formset:
EventFormSet = forms.models.inlineformset_factory(Event,
Actor,
can_delete = False,
extra = 2,
form = ActorForm)
but I get an error
<'class ctg.dtb.models.Actor'> has no ForeignKey to <'class ctg.dtb.models.Event'>
This isn't too surprising. The inlineformset worked for another set of models I had, but this is a different example. I think I'm going about it entirely wrong.
Overall question: How can I create a form that allows me to create a new Event and link it to an existing Actor?
Personally, I would put the ManyToMany on Event to begin with, but to each their own...
As for how to do it, you'd want to write a custom ModelForm (not an inline formset), let's call it EventForm. It would handle all of your event's fields and would also have a ModelChoiceField or ModelMultipleChoiceField to allow selection of the Actor(s) involved. Then in your view you would split out the processing of the Event fields and the ForeignKey/M2M field.
Make sense? alt text http://sonicloft.net/im/52