Django: Tastypie: Updating record after displaying it - django

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?

Related

Django Admin - Show form json input value as text insted string

Considering I have a model like:
MyStore = (
id = 1,
name = 'Foobar',
information_as_json = {
'open_at': datetime.now(),
'close_at': datetime.now() + timedelta('+1 day'),
'workers' : {
'Person1' : 'Owner',
'Person2' : 'Boss',
'Person3' : 'Boss',
}
})
Inside Django admin forms, for every field is generated an input, but for the field "information_as_json", I don't want to show it as a string or as JSON. That is because the users who are accessing this store admin page, need to read the field 'information_as_json' easier since no one can edit these values because it is generated in another part of the application.
Is it possible to convert these values to a "div" or a plain text? The contents would be:
This store opens at: {information_as_json.open_at}
This store close at: {information_as_json.close_at}
And for the workers, iterate through keys and values:
for key, value in information_as_json.workers:
Worker {key} has the role: {value}
I'm a beginner at Django, so I'm struggling a little with this part.
Every help would be appreciated :D
I would suggest approaching the model a little differently. Rather than storing the opening and closing hours as JSON they can just be fields directly on the store model. The the workers can be a JSONfield [docs] containing name/role pairs. If you're using PostgreSQL for your database you could even use HStoreField [docs], which might be more appropriate.
Here's how I would write a similar model.
class Store(models.Model):
name = models.CharField(max_length=512, unique=True)
workers = models.JSONField(blank=True, default=dict, editable=False)
closing = models.TimeField(blank=True, null=True, editable=False)
opening = models.TimeField(blank=True, null=True, editable=False)
To display the details in the Django admin we just need to define a property which returns the correct string.
#mark_safe
def details(self):
roles = [
f'{x} has the role: {y}'
for x, y in self.workers.items()
]
return '<br>'.join([
f'This store opens at: {self.opening:%-H:%M}',
f'This store closes at: {self.closing:%-H:%M}',
] + roles)
This method can then be referenced in the ModelAdmin and used like a read-only field.
#admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
list_display = ['name', 'opening', 'closing']
fields = ['name', 'details']
readonly_fields = ['details']

Generating a Django Model that can have multiple values in one field

I am trying to generate a Django model that can handle multiple values in a single field. As such, when the first field is queried through a view, a user should select a value for the second field through a select box.
To give a background of the problem, my seeding fixture looks like this...
[
{
"model":"myapp.location",
"pk":1,
"fields":{
"county": "countyname",
"places":{
"name": "placename",
"name": "placename",
"name": "placename",
"name": "placename",
"name": "placename"
}
}
}
]
In the above scenario, location is the intended name of my model. Now, through a form, I want a user to be presented with 'countynames'. Upon selecting a countyname, the user should be presented with 'placenames' in the selected county for them to choose.
I have tried the following format for the model...
class Location(models.Model):
county = models.CharField(max_length=100)
places = models.CharField(max_length=100, choices=places.name)
def __str__(self):
return self.countyname
Now, I know that the error that is thrown, ('places' is not defined), is warranted. I was asking whether there is a way to define it (places), as it is in the fixture, or if anyone has a better implementation for such a model... any alternative way is welcome and appreciated as I can't think of anything at this point.
So, after fiddling with two models and foreign keys as suggested in the comments above, I decided to amend the model, which also led to changing the fixture. I read about ArrayFields in Postgres + Django here. I amended the field 'places' to be an ArrayField as shown:
from django.contrib.postgres.fields import ArrayField
class Location(models.Model):
county = models.CharField(max_length=100)
places = ArrayField(models.CharField(max_length=100), blank=True)
def __str__(self):
return self.county
Next, it was just a matter of changing the JSON fixture to:
[
{
"model":"myapp.location",
"pk":1,
"fields":{
"county": "countyname",
"places":["placename","placename","placename","placename"]
}
}
]
After running python manage.py loaddata fixturename.json ,
it worked and the DB was seeded!

Customising the ID used in Marshmallow URLFor

I am using a Sqlite database, and flask, marshmallow, sqlalchemy to serve as a web api for a front end project.
I'm using a UUID stored as a blob in the database, and trying to stylise the data when its returned to the calling code.
I can convert the Id when its returned in the model using marshmallow's Function field, but using the HyperLinks, the Id is still being output as a byte array string:
class Item(Base):
__tablename__ = 'Items'
Id = Column(LargeBinary, primary_key=True)
def __repr__(self):
return '<Item {}>'.format(self.Name)
class ItemSchema(ma.Schema):
Id = fields.Function(lambda obj: str(UUID(bytes=obj.Id)))
_links = ma.Hyperlinks(
{"url": ma.URLFor("item_detail", id="<Id>"), "collection": ma.URLFor("items")}
)
class Meta:
fields = ("_links", "Id")
Is there a way to format the < Id > that is output in the links?
i.e
{"url": ma.URLFor("item_detail", id="<str(UUID(bytes=Id))>"), "collection": ma.URLFor("items")}
Here's how it is currently output:
{"_links": {"url": "/api/items/b%27%5Cx86%5Cxacx__%5Cxf9%5Cxc2J%5Cx80a6%5Cxa7%5Cx95%5Cx10%5Cx91%5Cxff%27", "collection": "/api/items"}, "Id": "86ac785f-5ff9-c24a-8061-36a7951091ff"}
I want it to look like:
{"_links": {"url": "/api/items/86ac785f-5ff9-c24a-8061-36a7951091ff", "collection": "/api/items"}, "Id": "86ac785f-5ff9-c24a-8061-36a7951091ff"}
I want the link to use the UUID format, not the byte array.
I found out the way around this. It was with the model set up and not marshmallow.
As the database was/is storing the pk as a blob, I was able to use SQLAlchemy_utils UUID column, setting the binary to true, and it all worked:
from sqlalchemy_utils import UUIDType
from uuid import UUID
....
Id = Column(UUIDType(binary=True), primary_key=True)

Translating between DRF's serializer and model design

I am using Django Rest Framework to support the use of Annotator.JS(http://annotatorjs.org/) in the frontend of my web application. The problem is the model I use to store any annotations that a user makes is different to the JSON that Annotator.JS sends from the front end in an AJAX request.
The structure of Annotator.JS' JSON is:
{
"id": "39fc339cf058bd22176771b3e3187329", # unique id (added by backend)
"annotator_schema_version": "v1.0", # schema version: default v1.0
"created": "2011-05-24T18:52:08.036814", # created datetime in iso8601 format (added by backend)
"updated": "2011-05-26T12:17:05.012544", # updated datetime in iso8601 format (added by backend)
"text": "A note I wrote", # content of annotation
"quote": "the text that was annotated", # the annotated text (added by frontend)
"uri": "http://example.com", # URI of annotated document (added by frontend)
"ranges": [ # list of ranges covered by annotation (usually only one entry)
{
"start": "/p[69]/span/span", # (relative) XPath to start element
"end": "/p[70]/span/span", # (relative) XPath to end element
"startOffset": 0, # character offset within start element
"endOffset": 120 # character offset within end element
}
],
"user": "alice", # user id of annotation owner (can also be an object with an 'id' property)
"consumer": "annotateit", # consumer key of backend
"tags": [ "review", "error" ], # list of tags (from Tags plugin)
}
The structure of my Annotation model is:
class Annotation(models.Model):
datapoint = models.ForeignKey('datapoint.Datapoint', related_name='%(class)s_parent_datapoint_relation')
owner = models.ForeignKey('users.User', related_name='%(class)s_creator_relation')
# Key fields from the Annotator JSON Format: http://docs.annotatorjs.org/en/latest/annotation-format.html
annotator_schema_version = models.CharField(max_length=8, blank=True)
text = models.TextField(blank=True)
quote = models.TextField()
uri = models.URLField(blank=True)
range_start = models.CharField(max_length=50, blank=True)
range_end = models.CharField(max_length=50, blank=True)
range_startOffset = models.BigIntegerField()
range_endOffset = models.BigIntegerField()
tags = TaggableManager(blank=True)
How can I create a serializer that can translate from the model structure to the JSON?
P.S. Annotator.JS allows the user to send extra information with the JSON structure noted above so the fact that Datapoint isn't included in the JSON structure isn't an issue. This can be passed along without any problem. Owner would hopefully equal User in the JSON.
Thanks for any help, it is greatly appreciated.
Just using a default ModelSerializer should get you serialization for all the simple fields for free (version, text, quote, uri), just by specifying which fields you want serialized. The other fields look straightforward as well:
To compose the ranges object, you can use a SerializerMethodField which lets you define a custom serializer method (return an array of dictionaries containing values from the range_* attributes on your model). Note that if you also need to be able to deserialize JSON into your model you'll need to define custom fields.
To go from owner -> user_id SlugRelatedField (assuming you can access the user id you want from the User object)
You can also use SlugRelatedField to go from TaggableManager -> ["tag", "values"] (again, assuming you can access the value you want through the model object managed by TaggableManager.
In general, everything you want is described in pretty good detail in the documentation.

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?