Django REST Framework and related models with one field - django

I want to create a simple REST API for the following model:
I have a main entity called Product with some fields: name, price...
Also I have a related entity called Keyword with one single field: the keyword.
Each product can have one or more keywords.
I can easily translate this into two django models:
class Product(models.Model):
name = models.CharField("Name of the product", max_length=100)
description = models.TextField("Description of the product")
price = models.IntegerField("Price of the product")
received_at = models.DateField("Received at")
class Keyword(models.Model):
keyword = models.CharField(max_length=50)
product = models.ForeignKey(Product, related_name="keywords")
But I'm lost with serializers.
I want a simple verb like this to create:
POST /products
{
"name": "My product name",
"description": "My product description",
"price": 40,
"received_at": "2015-12-1",
"keywords": ["keyword1", "keyword2"]
}
And the common list all, list one, update and delete:
GET /products
[{"name": "Product 1"...}, {"name": "Product 2"...}]
GET /products/1
{"name": "Product 1", "description": ...}
PUT /products/1
{
"name": "My product name",
"description": "My product description",
"price": 40,
"received_at": "2015-12-1",
"keywords": ["keyword2"]
}
DELETE /products/1
The problem is manage the keywords.
When a new product is created all keywords are created
When a product is updated I have to check which keywords are news and which keywords have been deleted.
Also I want my API can be extensible in the near future with more relations like the "Keyword relation" with a simple string field.
Any ideas?

Try implementing the underlying keywords as members of a set. Then you can simply call the add method with each new keyword and the set will keep it a unique grouping, by the nature of how sets work.
a = set()
a.add('keyword1')
a.add('keyword2')
a.add('keyword1')
print(a)
Output:
set(['keyword1', 'keyword2'])

Related

Populate Related Field Based on Query Parameter

I'm trying to implement a way to show full details of a related field, rather than just the ID or a specific field, when it is specified in a query parameter, e.g http://api_url/courses?populate="author"
Currently, the ID of the author field is shown like so, with this URL - http://api_url/courses
"data": [
{
"author": "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
....
}
]
I need it to be able to be displayed in full when a ?populate="author" parameter is added to the URL - http://api_url/courses?populate="author" should show this:
"data": [
{
"author": {
"id" : "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
"first_name" : "string",
"last_name" : "string",
},
....
}
]
I can currently show full details of the field by using a nested serializer, like so, or show just the UUID (or any other field) using a SlugRelatedField.
class CourseSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Course
fields = "__all__"
Any help is much appreciated.

Get field/column from related table and filter it

I have models:
class Animal(models.Model):
pass
class RegionAnimal(models.Model):
price = models.DecimalField(...)
animal = models.ForeignKey(Animal, ..., related_name="region_animals")
region = models.ForeignKey(Region, ...)
class LocationAnimal(models.Model):
count = models.PositiveIntegerField(default=0)
animal = models.ForeignKey(Animal, ..., related_name="location_animals")
location = models.ForeignKey(Location, ...)
I have a RegionAnimalViewSet(mixins.ListModelMixin, GenericViewSet) where I can filter QuerySet with animal and region fields using django_filters. I want to add to response count field from LocationAnimal using location field. How can I achieve this?
So, my request will be like:
{{ some_endpoint }}/?region=1&location=2
What I expect:
[
...,
{
"id": 1,
"region": {
# region details
},
"animal": {
# animal details
},
"price": 100, # animal price for particular region
"count": 123, # animal count for particular location
},
...
]
FYI: Some Regions have some Locations. Animal's price should be general for the whole Region. And Animal's count are different for the different Locations.
Thanks a lot!
You can easily have count in your DRF responses using pagination classes.
And it also works with django_filters perfectly.
Just checkout the DRF Pagination Document and configure proper FilterSet for the ModelViewSet and you'll be good to go.

Django: Tastypie: Updating record after displaying it

I have a very simple model:
class challenge(models.Model):
challenge = models.CharField(max_length=255)
timestamp = models.DateTimeField('date published',null=True,blank=True)
code = models.CharField(max_length=255)
And that model is linked via Tastypie.
By default the timestamp field is empty when you add an entry.
I would like to update the timestamp field with the time that the user first accessed the resource.
For example, if admin added data is:
Challenge: Do something
Timestamp: Null
Code: 123
Then after accessing /api/challenge/1/?format=json to have the output be:
{"challenge": "Do something", "code": "sasdasd", "id": 2, "resource_uri": "/api/challenge/2/", "timestamp": "2015-05-11T12:18:54"}
Is this possible using Tastypie and how?

Serializers in django rest framework with dynamic fields

I am trying to build a small api with django rest framework but I don't want to map directly the tables with calls (as in the examples).
I have the following database schema:
In models.py:
class ProductType(models.Model):
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class Product(models.Model):
#staticmethod
def get_accepted_fields(self):
return {'color': 'pink', 'size': 34, 'speed': 0, 'another_prop': ''}
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class ProductConfig(models.Model):
product_type = models.ForeignKey(ProductType)
product = models.ForeignKey(Product)
# a json field with all kind of fields: eg: {"price": 123, "color": "red"}
value = models.TextField(blank=True)
As you can see, every product can have multiple configurations and the value field is a json with different parameters. The json will be one level only. The configuration will have a flag if is active or not (so, the 1 product will have only 1 active configuration)
So, the data will look for example like this:
store_producttype
=================
1 type1
2 type2
store_product
=============
id name
1 car
store_productconfig
===================
id product_type_id product_id value active
1 2 1 { "color": "red", "size": 34, "speed": 342} 0
2 1 1 { "color": "blue", "size": 36, "speed": 123, "another_prop": "xxx"} 1
What I want to know is how can I get /product/1/ like this:
{
"id": 1,
"name": "car",
"type": "type1",
"color": "blue",
"size": 36,
"speed": 123,
"another_prop": "xxx",
}
and to create a new product posting a json similar with the one above.
The json fields are defined but some of them can miss (eg: "another_prop" in the productconfig.id=1
On update, anyway, it will create a new row in productconfig and it will put inactive=0 on the previous one.
So, every product can have different configuration and I want to go back to a specific configuration back in time in some specific cases). I am not really bound to this data model, so if you have suggentions for improvement I am open to them, but I don't want to have that properties as columns in the table.
The question is, what will be the best way to write the serializers for this model? There is any good example somewhere for a such use case?
Thank you.
Let's take this step by step:
In order to get a JSON like the one you posted, you must first transform your string (productConfig value field) to a dictionary. This can be done by using ast.literal_eval ( see more here).
Then, in your product serializer, you must specify the source for each field, like this:
class ProductSerializer(serializers.ModelSerializer):
color = serializer.Field(source='value_dict.color')
size = serializer.Field(source='value_dict.size')
type = serializer.Field(source='type.name')
class Meta:
model = Product
fields = (
'id',
'color',
'size',
'type',
)
This should work just fine for creating the representation that you want. However, this will not create automatically the product config, because DRF doesn't yet allow nested object creation.
This leads us to the next step:
For creating a product with a configuration from JSON, you must override the post method in your view, and create it yourself. This part shouldn't be so hard, but if you need an example, just ask.
This is more of a suggestion: if the json fields are already defined, wouldn't it be easier to define them as separate fields in your productConfig model?

Django fixtures primary key error, need natural keys solution

So I have a Film model that holds a list of Actors model in a many to many field:
class Person(models.Model):
full = models.TextField()
short = models.TextField()
num = models.CharField(max_length=5)
class Film(models.Model):
name = models.TextField()
year = models.SmallIntegerField(blank=True)
actors = models.ManyToManyField('Person')
I'm trying to load some initial data from json fixtures, however the problem I have is loading the many to many actors field.
For example I get the error:
DeserializationError: [u"'Anna-Varney' value must be an integer."]
with these fixtures:
{
"pk": 1,
"model": "data.Film",
"fields": {
"actors": [
"Anna-Varney"
],
"name": "Like a Corpse Standing in Desperation (2005) (V)",
"year": "2005"
}
while my actors fixture looks like this:
{
"pk": 1,
"model": "data.Person",
"fields": {
"full": "Anna-Varney",
"num": "I",
"short": "Anna-Varney"
}
}
So the many to many fields must use the pk integer, but the problem is that the data isn't sorted and for a long list of actors I don't think its practical to manually look up the pk of each one. I've been looking for solutions and it seems I have to use natural keys, but I'm not exactly sure how to apply those for my models.
EDIT: I've changed my models to be:
class PersonManager(models.Manager):
def get_by_natural_key(self, full):
return self.get(full=full)
class Person(models.Model):
objects = PersonManager()
full = models.TextField()
short = models.TextField()
num = models.CharField(max_length=5)
def natural_key(self):
return self.full
But I'm still getting the same error
There's a problem with both the input and the natural_key method.
Documentation: Serializing Django objects - natural keys states:
A natural key is a tuple of values that can be used to uniquely
identify an object instance without using the primary key value.
The Person natural_key method should return a tuple
def natural_key(self):
return (self.full,)
The serialised input should also contain tuples/lists for the natural keys.
{
"pk": 1,
"model": "data.film",
"fields": {
"actors": [
[
"Matt Damon"
],
[
"Jodie Foster"
]
],
"name": "Elysium",
"year": 2013
}
}