model field named url and django rest framework url - django

I started to use the great django-rest-framework days ago.
I'm not able to solve this simple issue.
My model contains a models.URLField named url.
My serializers.py file:
class ModelSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.Field(source='owner.username')
class Meta:
model = Model
fields = ('url', 'owner', 'title', 'abstract', 'category', 'position', 'param1')
Checking the API result the field 'url' is populated with model.URLField.
"results": [
{
"url": "http://www.web.com",
"owner": "me",
"title": "title of the stuff"
}
Instead I would like to have
"results": [
{
"url": "http://localhost:8000/en/apiv1/maps/4/",
"url_in_model": "http://www.web.com",
"owner": "me",
"title": "Forest fire"
}
How can I solve?
Thanks

It may be considered poor form (I am by no means a pro programmer or rest_framework expert), but I believe you can add extra context to the serialized output:
http://django-rest-framework.org/api-guide/serializers.html#specifying-fields-explicitly
class AccountSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
groups = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Account
Extra fields can correspond to any property or callable on the model.
So in the above the field 'get_absolute_url' must be in the 'Account' model.
In your case (I think) you could do this:
class ModelSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.Field(source='owner.username')
url_in_model = serializer.Field(source='url')
class Meta:
model = Model
fields = ('url', 'url_in_model', 'owner', 'title', 'abstract', 'category', 'position', 'param1')
Of course you would pick the field type that suits.
I haven't had the chance to test this, so there is the chance that using 'url' as your source causes an issue and you may want to name your model field something else - apologies if that is the case and I have wasted your time.
Hope I have helped.

The accepted answer didn't work for me in DRF 3. I got my model's url data for both url and url_in_model. To get the correct url value from DRF, it looked like:
class AccountSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='account-detail')
url_in_model = serializer.URLField(source='url')
class Meta:
model = Account
account-detail should be replaced with whatever view corresponds to a single Account.

I personally prefer to set the default hyperlinked field to another name altogether.
You can do this via the URL_FIELD_NAME setting.
Source: http://www.django-rest-framework.org/api-guide/serializers/#changing-the-url-field-name
e.g: URL_FIELD_NAME = 'key' (default is 'url')

The easiest way to avoid the conflict is to set URL_FIELD_NAME in settings.py like:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
"URL_FIELD_NAME": "api_url", // <---- add this
}

the above answers are not precise.It is simply to control the name of url(default is 'url') by using the key "url_field_name".
class UserSerializer(serializers.ModelSerializer):
url_field_name='test-url'
class Meta:
model = UserInfo
extra_kwargs = {
'test-url': {
'view_name': '<your view name defined on urls.py>',
'lookup_field': '<lookup_field>',
'lookup_url_kwarg': '<lookup_url_kwarg>'
}
}
fields = ['test-url'] #remember include your defined url name
the source code of ModelSerializer in serializer.py
def get_fields(self):
"""
Return the dict of field names -> field instances that should be
used for `self.fields` when instantiating the serializer.
"""
if self.url_field_name is None:
self.url_field_name = api_settings.URL_FIELD_NAME
assert hasattr(self, 'Meta'), (
'Class {serializer_class} missing "Meta" attribute'.format(
serializer_class=self.__class__.__name__
)
)
assert hasattr(self.Meta, 'model'), (
'Class {serializer_class} missing "Meta.model" attribute'.format(
serializer_class=self.__class__.__name__
)
)
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
declared_fields = copy.deepcopy(self._declared_fields)
model = getattr(self.Meta, 'model')
depth = getattr(self.Meta, 'depth', 0)

Nginx needs to be configured correctly, put your ip in the PROXY_PASS
location / {
proxy_pass http://127.0.0.1:8003;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
}
Change http://127.0.0.1:8003 by http://this-is-my-ip-address.com/[port]

Related

Data does not get saved in database ; unexpected keyword arguments

I have gone through many solutions posted on SO and other places but have been running into the same issue. Pretty new to django and trying to understand where I am going wrong
I am getting the following error
TypeError: Basetable() got unexpected keyword arguments:
'CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents'
I have the following JSON Data
jsonToUse = {
"CompanyId": "320193",
"CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents": [
{
"decimals": "-6",
"unitRef": "usd",
"value": "39789000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "50224000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "25913000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "35929000000"
}
]
}
Model:
class Basetable(models.Model):
basetable_id = models.AutoField(primary_key=True)
CompanyId = models.IntegerField()
class Cashcashequivalentsrestrictedcashandrestrictedcashequivalents(models.Model):
cashcashequivalentsrestrictedcashandrestrictedcashequivalents_id = models.AutoField(
primary_key=True)
unitRef = models.CharField(max_length=100)
value = models.CharField(max_length=100)
decimals = models.IntegerField()
basetable_id = models.ForeignKey(Basetable, on_delete=models.CASCADE)
Serializer:
class CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsSerializer(serializers.ModelSerializer):
class Meta:
model = Cashcashequivalentsrestrictedcashandrestrictedcashequivalents
fields = ['decimals', 'unitRef', 'value']
class CashFlowSerializer(serializers.ModelSerializer):
CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents = CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsSerializer(
many=True)
class Meta:
model = Basetable
fields = "__all__"
View:
.....#TRIMMED GET SYNTAX.....
check = CashFlowSerializer(data=jsonToUse)
if (check.is_valid(raise_exception=True)):
print("ready to send to db")
check.save()
return JsonResponse(jsonToUse, safe=False)
I want to save the data in the database for the provided JSON
Have a look to the full traceback error, the problem is that you need to define your own create method in your nested serializer:
TypeError: Got a `TypeError` when calling `Basetable.objects.create()`.
This may be because you have a writable field on the serializer class that is not a valid argument to `Basetable.objects.create()`.
You may need to make the field read-only, or override the CashFlowSerializer.create() method to handle this correctly.
...
TypeError: Basetable() got an unexpected keyword argument 'CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents'
You will find more information in django-rest-framework Writable nested serializers documentation.
Basically, iterate on your JSON data and create Cashequivalents objects.

Partially updating a model with BooleanFields with djangorestframework

I have a model like this
class Status(models.Model):
is_working = models.BooleanField(
default=None,
null=True
)
is_at_home = models.BooleanField(
default=None,
null=True
)
with the corresponding serializer
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
using the default ModelViewSet
class StatusViewSet(viewsets.ModelViewSet):
"""
"""
serializer_class = StatusSerializer
queryset = Status.objects.all()
Whenever I partially update a Status, by e.g calling the put method on the API, all other fields are reset to False instead of keeping their old value.
Say, I have a Status that looks like this:
{
"id": 1,
"is_working": null,
"is_at_home": null,
}
If I call put using the following JSON:
{
"is_working": true
}
my data now looks like this
{
"id": 1,
"is_working": true,
"is_at_home": false <-- GOT UPDATED
}
I however, just want to update the is_working field, so that my desired result would be:
{
"id": 1,
"is_working": true,
"is_at_home": null
}
This probably has to do with the fact that HTML forms don't supply a value for unchecked fields. I, however, don't use any HTML forms as I'm purely consuming the API through JSON requests.
Using the serializer as is, I'd need to perform an extra get request prior to updating the model, just to get the state of the fields I don't want to update.
Is there a way around that?
First off, for partially updating you need to have a PATCH request and not PUT.
Since you are using ModelViewSet drf should automatically recognize that and set partial=True and update only the fields which were sent in the api payload.
ModelViewSet doc- DRF
You can make your is_at_home field read only in serializer as PUT method is updating it. Try this in your serializer.Hope this will work for you.
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
extra_kwargs = {
'id': {'read_only': True},
'is_at_home': {'read_only': True},
}

How do I create new models not affecting the nested serializer

I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()

serializer.data is missing some of the data

Context: I am having problem accessing fields which are validated by nested serializers.
I have a very sample model as shown below.
For 2 of the fields I have their specific serializers. When I try to access the data it returns all the fields except the one validated by the specific serializers.
Models looks like this
class Sampler(models.Model):
sample_name = models.CharField(unique=True, max_length=100)
sampling_by = JSONField(max_length=100)
extractions = JSONField(max_length=100)
max_samples = models.IntegerField(default=100)
Serializers
class ExtractionsSerializer(serializers.BaseSerializer):
table_name = serializers.CharField()
extraction_type = serializers.ChoiceField(["ABC"])
dependencies = serializers.ListField(child=RecursiveField(), allow_empty=True, required=False)
class SamplingBySerializer(serializers.BaseSerializer):
"""
Validate sampling_by
"""
def to_internal_value(self, samples):
methods = ["ABC"]
sampling_not_supported = [sample for sample in samples
if sample['by'] not in methods]
if sampling_not_supported:
raise ValidationError("{} not in {}".format(sampling_not_supported, methods))
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
class Meta:
model = Sampler
fields = ('sample_name', 'database', 'schema', 'sampling_by', 'extractions', 'max_samples')
Now I do
data = {
"database": "postgresql://..",
"sampling_by":[{
"by":"ABC",
"value": ["l32=turn_the"]
}],
"max_samples":3,
"extractions" : [{
"table_name": "person",
"extraction_type": "ABC"
}]
}
sampler = SamplerSerializer(data=data)
sampler.is_valid() #returns True
sampler.data => does not contain data of the nested fields. Like the `sampling_by` and `extractions`. Contains all other fields
sampler.validated_data => same problem as above
Any help would be appreciated! thanks
You probably missed the fact that your nested serializers are flagged as read_only=True
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
Remove that part, implement the serializer's create / update and you're good to go.
On a side note, it doesn't make sense to access serializer.data when deserializing.
Edit: the authority source is validated_data.

Django tastypie model attribute of format

Simple problem. What happens when you have the name "format" as an attribute of a model with TastyPie?
How do you handle the query for http://0.0.0.0:9000/api/v1/library_type/?format=json? when you have a Model which looks like this.
class LibraryType(models.Model):
"""The information about each library type."""
format = models.IntegerField(choices=LIBRARYTYPE_CHOICES)
equiv = models.IntegerField()
name = models.CharField(max_length=96)
prefix = models.CharField(max_length=96)
description = models.CharField(max_length=255, db_column='remark')
You end up with:
{
"error": "Invalid resource lookup data provided (mismatched type)."
}
Obviously this makes sense but how do you work with it? The corresponding Resource definition.
class LibraryTypeResource(ModelResource):
class Meta:
queryset = LibraryType.objects.all()
resource_name = 'library_type'
list_allowed_methods = ['get',]
detail_allowed_methods = ['get', ]
filtering = {
'id': ('exact', ),
'name': ALL,
'format': ALL,
'prefix': ALL,
'description': ALL,
'site': ALL_WITH_RELATIONS,
}
Are you using only the json format?
If so, you can use the TASTYPIE_DEFAULT_FORMATS to set it as json and never use format=json again.
If not, you can use one of tastypie's hooks to retrieve the format query param and do whatever you want with it.
I would change the name of the query param.