I wrote an __init__ method for one of my models that adds some auxiliary information to the object by dynamically adding an attribute to the object that does not reflect a column in the database:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def __init__(self, *args, **kwargs):
self.aux_info = "I'm not in the database!"
This seemed to be working fine, but I found a case where it does not work. I have some code in a view where I set a status variable and package up a list of MyModels into json like so:
from django.core import serializers
from django.utils import simplejson
...
# have to use serializers for django models
serialized_items = serializers.serialize("json", itemlist)
data["items"] = serialized_items # serialized_items is now a string
data["status"] = status
# package up data dict using simplejson for python objects
resp = simplejson.dumps(data)
return HttpResponse(resp, mimetype="application/javascript")
The problem seems to be that django's serializers only serialize the model fields and not all attributes of the object so aux_info does not come through. I'm also pretty sure that using both serializers and simplejson is not the right way to do this. Thanks for any help!
Try usung the serialiser's optional fields argument.
serialized_items = serializers.serialize("json", itemlist, fields=['.....', 'aux_info'])
May i also suggest that using the __init__ method to add fields is considdered bad form in django and would be much better achieved like so:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def aux_info(self):
return "I'm not in the database!"
Related
I have a view in Django that fetches some objects, adds new attribute to them and returns them as JSON response.
The code looks like this:
def stats(request):
items = MyItem.objects.all().order_by('-id')
for item in items:
item.new_attribute = 10
items_json = serializers.serialize('json', items)
return HttpResponse(items_json, content_type='application/json')
The new_attribute is not visible in JSON response. How can I make it visible in JSON response? It can be accessed in templates normally with {{ item.new_attribute }}.
EDIT: I'm using default Django serializer (from django.core import serializers)
Default serializer looks into fields defined in model. Thus, there are two options to resolve it:
Add the attribute to your model MyItem
Use a custom serializer to serialize your model with this dynamic attribute
Example of such serializer:
serializers.py
class MyItemSerializer(serializers.ModelSerializer):
new_attribute = serializers.SerializerMethodField()
def get_new_attribute(self, obj):
if hasattr(obj, 'new_attribute'):
return obj.new_attribute
return None
class Meta:
model = MyItem
fields = '__all__'
And then in views.py it will be:
items_json = serializers.MyItemSerializer(items, many=True).data
Since you are reference serializers in your code, I assume that you are using Django REST Framework and have a class named MyItemSerializer. This class tells is used by serializers.serialize() to determine how to create the JSON string from your model objects. In order to add a new attribute to the JSON string, you need to add the attribute to this class.
I am trying to post to my API with foreign key relationships. It's throwing me back an error saying it's expecting a dictionary as opposed to int for character, character_opponent and stage. This is because the way my models are set up. They have foreign key relationships. The model in question looks like this:
import uuid
from django.db import models
from django.utils import timezone
from analysis.models import Analysis
from characters.models import Character
from stages.models import Stage
class Match(models.Model):
analysis = models.ForeignKey(Analysis, on_delete=models.CASCADE)
character = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character')
character_won = models.BooleanField()
character_opponent = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character_opponent')
character_opponent_won = models.BooleanField()
created_at = models.DateTimeField(editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
updated_at = models.DateTimeField(editable=False)
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
def __str__(self):
return '%s vs. %s on %s' % (self.character, self.character_opponent, self.stage)
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.created_at:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(Match, self).save(*args, **kwargs)
class Meta:
db_table = "matches"
And here is my serializer:
from rest_framework import serializers
from matches.models import Match
from characters.serializers import CharacterSerializer
from stages.serializers import StageSerializer
class MatchSerializer(serializers.ModelSerializer):
character = CharacterSerializer()
character_opponent = CharacterSerializer()
stage = StageSerializer()
class Meta:
model = Match
fields = ('id', 'analysis', 'character', 'character_won', 'character_opponent', 'character_opponent_won', 'stage')
Is there some option I am missing here to be able to post properly? Clearly I shouldn't have to pass the entire character object each time I want to post something, right? I should just be able to pass the primary key.
From your few comments I understood that you need nested serializer in GET method. What I suggest is, use two[or more] serializers for your API class.
Assuming you are using ModelViewSet API class is using,then you could override get_serializer_class() method as below,
from rest_framework.viewsets import ModelViewSet
class MatchAPI(ModelViewSet):
queryset = Match.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return MatchCreateSerializer
return MatchSerializer
And your MatchCreateSerializer will be like this,
class MatchCreateSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Match
Thus, you only need to provide the PKs of analysis,character etc while creation of Match instance
It will come down to your CharacterSerializer and StageSerializer. If you want to input 1 format (using serialisers.PrimaryKeyRelatedField()), but output another (CharacterSerializer, StageSerializer), you might be best served using 2 serialisers and switching in your view.
In your view you can override get_serializer_class and check your request method, or in the case of a viewset you can check the method being invoked.
When you declare a serializer related field using another serializer, like this
character = CharacterSerializer()
you are telling django-rest-framework that you want a nested serializer. What you want is something like this instead
character = serializers.PrimaryKeyRelatedField()
or you can actually just leave the explicit field declaration out of the serializer (since this is the default), see the doc on serializer relations.
In Django, is there a way to create a object, create its related objects, then save them all at once?
For example, in the code below:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
body = models.CharField(max_length=255)
class Tag(models.Model):
post = models.ForeignKey(Post)
title = models.CharField(max_length=255)
post = Post(title='My Title', body='My Body')
post.tag_set = [Tag(post=post, title='test tag'), Tag(post=post, title='second test tag')]
post.save()
I create a Post object. I then also want to create and associate my Tag objects. I want to avoid saving the Post then saving the Tags because if a post.save() succeeds, then a tag.save() fails, I'm left with a Post with no Tags.
Is there a way in Django to save these all at once or at least enforce better data integrity?
transactions to the rescue !
from django.db import transaction
with transaction.atomic():
post = Post.objects.create('My Title', 'My Body')
post.tag_set = [Tag(post, 'test tag'), Tag(post, 'second test tag')]
As a side note: I think you really want a many to many relationship between Post and Tag...
override save...
class Post(models.Model):
...
def save(self, *args, **kwargs):
super(Post, self).save(*args, **kwargs)
for tag in self.tag_set:
tag.save()
This way you don't have to write the transaction thing over and over again. If you want to use transactions, just implement it in the save method instead of doing the loop.
Let's say I have a Django model
class Book(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
is_on_loan = models.BooleanField()
How do I get the JSON representation of the Book? I could easily write something like
import json
def get_json(self):
json_rep = {}
json_rep['title'] = self.title
json_rep['author'] = self.author.full_name
json_rep['is_on_loan'] = self.is_on_loan
return json.dumps(json_rep)
But is there a better way?
This is called serializing. There are django native serializers, you can read about them in the docs (https://docs.djangoproject.com/en/1.9/topics/serialization/), but the basic usage is
from django.core import serializers
data = serializers.serialize("json", YourModel.objects.all())
That said, Django serializers have some limitations that might or might not cause problems for you.
If you are using or considering using Django REST Framework, it has excellent, flexible serializer/deserializer architecture. There are other serializing libraries out there that you might want to google for.
Yes there is a better way, use the built-in Django JSON serializer
from django.core import serializers
json_data = serializers.serialize('json', Book.objects.all())
I am using django-model-utils for inheritance Managers. I want to get results of only one subclass at a time.
managers.py
from model_utils.managers import InheritanceManager
class PostManager(InheritanceManager):
pass
models.py
from .managers import PostManager
class Post(models.Model):
title = models.CharField(max_length=20)
text = models.TextField()
objects = PostManager()
class ImagePost(Post, models.Model):
source = models.URLField()
image = models.ImageField(upload_to="images/%Y/%m/%d")
class VideoPost(Post, models.Model):
source = models.URLField()
I want to return results of only image type. by writing a simpler query like this.
Post.objects.filter(type='image').select_subclasses()
What i have tried:
if type == 'image':
Post.objects.filter(imagepost__isnull=False).select_subclasses()
This works but is kind of anti-pattern, i don't want to write conditions in views for every content type.
Is there better way like defining a property in models or converting it into a manager method? or am i missing something?
Have you tried to pass the class to select_subclasses method?
Post.objects.select_subclasses(ImagePost)
Check their doc about this feature.
Edit:
I misunderstood the question, but sounds like OP wants only the Post with type ImagePost. Doing select_subclasses(ImagePost) would fetch everything and convert the objects with type ImagePost to ImagePost instances. The solution should be as simple as :
image_posts = ImagePost.objects.all()