I have a TimeField in my models.py that uses choices of the form
MORNING = datetime.time(4, 0)
NIGHT = datetime.time(20, 0)
TIME_CHOICES = (
(MORNING, _('Morning')),
(NIGHT, _('Night'))
)
time = models.TimeField(_('Time'), choices=TIME_CHOICES)
serializers.py
class VehiclePlanningSerializer(serializers.ModelSerializer):
class Meta:
model = mymodel
When i create objects via django admin and set the choice to Morning it stores a proper time "04:00:00" in the db. When i use raw data in the API GUI and insert "04:00:00" for time it works also. But when i use the Form on the API GUI and select Morning I get the following error:
"time": [
"\"4 a.m.\" is not a valid choice."
]
So somehow the serializer transforms the Morning choice wrongly when he gets a time(4,0). Is there a way to suppress this transformation?
Related
I'm learning and new to Django Rest Framework and I'm having an issue in serializer validations and ListSerializer update method.
I have an APIView class which handles the put request. I just wanted to have a custom validation and so I've overridden validate method in the serializer class.
From postman I'm sending a JSON data to this APIView Class.
Sample JASON data:
[
{
"id": 1,
"ip": "10.1.1.1",
"host_name": "hostname 1"
},
{
"id": 2,
"ip": "10.1.1.2",
"host_name": "hostname 2"
}
]
When I receive the data and do serializer.is_valid() it passes the flow to the overridden validate function. But in there when I check for the attrs argument, I get all the fields except the id. The key and value for id are not present. It shows None.
The same issue occurred to me when I was trying to override the update method in ListSerializer.
when I tried the below code in the ListSerializer's update method,
data_mapping = {item['id']: item for item in validated_data}
I got an error saying KeyError 'id'.
It seems it's not accepting id field and I'm not sure why! Please, someone explain this to me if I'm wrong anywhere.
Serializer class
from rest_framework import serializers
from .models import NoAccessDetails
class NoAccessDetailsListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data)
data_mapping = {data.id: data for data in instance}
#Here I'm getting KeyError ID
validated_data_mapping = {item['id']: item for item in validated_data}
return
class NoAccessDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = NoAccessDetails
list_serializer_class = NoAccessDetailsListSerializer
fields = ("id", "ip", "host_name")
def validate(self, data):
id_val = data.get('id')
ip = data.get('ip')
host_name = data.get('host_name')
#here the id value is None
print('id val {} '.format(id_val))
return data
If I am understanding correctly, the issue is that you do not see the id field inside of validated_data. If so, I believe this is intentional in the framework:
https://github.com/encode/django-rest-framework/issues/2320
Basically, the id field is read_only by default. Let me know if you have questions that are not answered by Tom's response to that issue.
EDIT: Also feel free to share the higher level use case (what you are planning on doing with the ID inside of validation), and maybe we can offer alternative approaches.
I have an existing mongo document which has been exposed over a REST API. The API request will contain certain fields from the document which either needs to be updated with new values or insert new values in them if the field is null. How to perform the update on fields of an existing mongoengine document? I'm using marshmallow-mongoengine for serialization on flask.
The problem that I'm facing is that if a certain field is missing in the request payload, on calling update with the remaining fields as kwargs leads to setting the missing fields as None. How can update or insert only the fields given in the payload?
Joseph's answer is OK. But another answer wont hurt eh!
Here's how i updated my document using flask-mongoengine
Actual code :
Game.objects(id = _id).update(
set__kickoff = request_json.get('kickoff'),
set__gameid = request_json.get('gameid'),
set__home_team = request_json.get('home_team'),
set__away_team = request_json.get('away_team'),
set__home_win = request_json.get('home_win'),
set__draw = request_json.get('draw'),
set__away_win = request_json.get('away_win'),
set__sport = request_json.get('sport')
)
Game class :
import datetime
flask_mongoengine import BaseQuerySet, MongoEngine
db = MongoEngine()
class Game(db.Document):
kickoff = db.DateTimeField(required=True)
added_on = db.DateTimeField(default=datetime.datetime.utcnow)
gameid = db.FloatField(required=True)
home_team = db.StringField(required=True)
home_win = db.FloatField(required=True)
draw = db.FloatField(required=True)
away_win = db.FloatField(required=True)
away_team = db.StringField(required=True)
sport = db.StringField(required=True)
meta = {
'collection':'games',
'queryset_class': BaseQuerySet
}
PS : Remember to indent the code in python
Additionally I noticed you tagged Marshmallow in your question. Here, a sample derived from their official git repo here
First we need a Mongoengine Document:
import mongoengine as me
class Task(me.EmbeddedDocument):
content = me.StringField(required=True)
priority = me.IntField(default=1)
class User(me.Document):
name = me.StringField()
password = me.StringField(required=True)
email = me.StringField()
tasks = me.ListField(me.EmbeddedDocumentField(Task))
Great ! Now it's time for the Marshmallow Schema. To keep things DRY, we use marshmallow-mongoengine to do the mapping:
import marshmallow_mongoengine as ma
class UserSchema(ma.ModelSchema):
class Meta:
model = User
Finally it's time to use our schema to load/dump documents:First let's create a document
user_schema = UserSchema()
u, errors = user_schema.load({"name": "John Doe", "email":
"jdoe#example.com", "password": "123456","tasks": [{"content": "Find a
proper password"}]})
u.save()
If the document already exists, we can update it using update
u
u2, errors = user_schema.update(u, {"name": "Jacques Faite"})
>>> u2.name
"Jacques Faite"
If you only want to update one single document you can use the save method. That's what I do. If a document already exists, it updates fields instead of creating a new document.
car = Car.objects(pk=car_id) # return a queryset
if car:
car = car.get(pk=car_id) # return an object from queryset
car.make = requestData['make']
car.model = requestData['model']
car.mileage = requestData['mileage']
car.save()
If you want to update many documents then, I recommend checking out the atomic updates section of the user guide.
Something like~
Car.objects(param="param to filter by").update(set__param=newParam)
"set" followed by two underscores is a modifier. There are more modifiers available in the guide I linked above.
I have a model with a DateTimeField:
class MyShell(models):
created = models.DateTimeField(auto_now=true)
I have an api linked to it using Django Rest Framework:
class ShellMessageFilter(django_filters.FilterSet):
created = django_filters.DateTimeFilter(name="created",lookup_type="gte")
class Meta:
model = ShellMessage
fields = ['created']
class ShellListViewSet(viewsets.ModelViewSet):
"""
List all ShellMessages
"""
serializer_class = ShellMessageSerializer
queryset = ShellMessage.objects.all()
filter_class = ShellMessageFilter
When I hit my API using the following URL it works perfectly:
http://127.0.0.1:8000/api/shell/?created=2014-07-17
# It returns all shell with a date greater than the one provided in URL
But, I want to do more than that by filtering base on a date and a time. I tried the following URL without success:
http://127.0.0.1:8000/api/shell/?created=2014-07-17T10:36:34.960Z
# It returns an empty array whereas there are items with a created field greater than 2014-07-17T10:36:34.960Z
If you guys know how to proceed... I don't find any good informations or example in django-filters documentation...
Simpler solution if you don't care about fractions of seconds: replace the "T" with space (%20):
http://127.0.0.1:8000/api/shell/?created=2014-07-17%2010:36:34
Worked for me.
This may not be what you want, but you could simply convert from Unix time. E.g.:
def filter_unix_dt(queryset, value):
if not value:
return queryset
try:
unix_time = int(value)
t = datetime.fromtimestamp(unix_time)
result = queryset.filter(created__gte=t)
return result
except ValueError:
return queryset
class ShellMessageFilter(django_filters.FilterSet):
created = django_filters.DateTimeFilter(action=filter_unix_dt)
class Meta:
model = ShellMessage
fields = ['created']
The issue and solution are documented in this DRF issue page: https://github.com/tomchristie/django-rest-framework/issues/1338
TL;DR: A Django ISO conversion 'issue' is preventing DRF from working as you are expecting. A fix for this has been written in DRF, allowing you to use IsoDateTimeField instead of DateTimeField. Just replaying the T with a space in your request param value also works.
I have a ModelForm class in which I set a couple of the fields as ChoiceField. For one of my views, I'd like to create a form from my ModelForm class that pulls from an instance of my model in the database (like so):
form = MyModel(instance=model_instance)
When I do this and then render the form in a template, I've noticed that most of the fields are pre-populated with values pulled from the model instance, which is what I want. However, this isn't the case for two ChoiceField fields. These render as drop-down select menus with no specific option selected.
What's strange is if I don't define those two fields as ChoiceField-type in my ModelForm class, they render as normal text input fields in HTML and pre-populate using the database values. But when I define them so they show up as select-option input fields in HTML, nothing is pre-selected. Can I change this so that the values from the database are pre-selected?
EDIT: As requested here is the code for my model and form:
class App(models.Model):
CODES = (
(u'a',u'annual'),
(u'm',u'monthly'),
(u'w',u'weekly')
)
code = models.CharField(max_length=1, choices=CODES)
start_time = models.TimeField(blank=True, null=True)
end_time = models.TimeField(blank=True, null=True)
class AppForm(ModelForm):
CODES = (
(u'',u'Please select code'),
(u'a',u'annual'),
(u'm',u'monthly'),
(u'w',u'weekly')
)
TIMES = (
(u'00:00',u'All Day'),
(u'12:00',u'Noon')
)
start_time = forms.ChoiceField(required=False, choices=TIMES)
end_time = forms.ChoiceField(required=False, choices=TIMES)
code = forms.ChoiceField(choices=CODES, label='Type')
class Meta:
model = App
Interestingly, code field has the model instance value preselected just fine when rendered as HTML. I wonder if having the choices argument in the model definition makes the difference here?
UPDATE: I just noticed that if I pull up an App instance in the python manage.py shell like so:
a = App.objects.get(id=16)
a.start_time
I get a value like datetime.time(12, 0). But in the Django admin, when I'm looking at all of the App instances, all of them show (None) under start_time and end_time. Why would that be?
In response to your update : your times strings match default time string HH:MM format. Just like a user would enter them from website manually 12:00. The values get parsed and turned into time at model save (at validating really).
And when you load model - then of course the initial values loaded from object match the field's (models.TimeField) type.
If you replace your TIMES with
(datetime.time(0,0),u'All Day'),
(datetime.time(12,0),u'Noon')
your troubles should be over.
Alan
I have a a form in Django with two inline forms. One of them is giving me grief.
My model is like so.
class BookingActivity(models.Model):
booking = models.ForeignKey('Booking')
program = models.ForeignKey(Program)
activity = models.ForeignKey(Activity, choices=programs_as_optgroups())
the activity ForeignKey choices are generated via this method:
def programs_as_optgroups():
activities = []
programs = []
for program in Program.objects.all():
new_program = []
new_activities = []
for activity in Activity.objects.filter(program=program):
new_activities.append([activity.id, activity.name])
new_program = [program.name, new_activities]
activities.append(new_program)
return activities
I'm trying to add <optgroup> tags to my ForeignKey select which is working. But when I submit the form I get an error: Cannot assign "u'3'": "BookingActivity.activity" must be a "Activity" instance.
This makes some sense - sort of. But if I check the request data sent from the form post. With choices either setup or not I get the same values, i.e.
activity = models.ForeignKey(Activity, choices=programs_as_optgroups())
and
activity = models.ForeignKey(Activity)
both return the a u'3' from the form. But I can't figure out why I get an error only when I'm using the optgroups.
I'm guessing you're trying
http://dealingit.wordpress.com/2009/10/26/django-tip-showing-optgroup-in-a-modelform/
in the blog
sub_categories.append([sub_category.id, sub_category.name])
you have
new_activities.append([activity.id, activity])
I think you're assuming you will get an object when it actually is a string you're getting back.