I am having struggles with the alteration on the parameter name during serialization with DRF.
My input would be a JSON with some parameters:
{
"limit": 10,
"type": "group",
[...]
}
and my serializer looks like:
class RankSerializer(serializers.Serializer):
limit = serializers.IntegerField(default=100, min_value=1)
type = serializers.CharField()
def validate_type(self, t):
# validation
But this doesn't sound right. Type is a reserved keyword in Python so I don't want to use it as a parameter name. I'd like to somehow map it to i.e result_type or something like this.
I already tried using the source= parameters as follows:
result_type = serializers.CharField(source='type')
but this doesn't seem to work on non-model inputs.
I cannot rename the parameter on the frontend level.
I'd appreciate any tips regarding this issue. Cheers.
I searched https://www.django-rest-framework.org/api-guide/fields/ of DRF docs but was unable to find a viable solution. So I subclassed the Serializer class provided by DRF and created a CustomSerializer. In that, I subclassed the run_validation method. In that, before calling super().run_validation(), I access the initial_data passed and change it to the desired mapping as you mentioned. This information of field mappings I store in the Meta class nested inside RankSerializer. So for example, you have fields F1, F2, F3(in JSON data, for example) whose values you want to populate in fields named M1, M2, M3, you just have to write in the Meta class in the field_mappings dictionary the following :
field_mappings = {
'M1': 'F1',
'M2':'F2',
'M3': 'F3'
}
The other fields will function normally.
Here is the code
import rest_framework.serializers as serializers
from .models import Rank
from rest_framework.fields import empty
class CustomSerializer(serializers.Serializer):
def run_validation(self, data=empty):
for (field, mapping) in self.Meta.field_mappings.items():
data[field] = data[mapping]
del data[mapping]
return super().run_validation(data=data)
class RankSerializer(CustomSerializer):
limit = serializers.IntegerField(default=100, min_value=1)
result_type = serializers.CharField(required=True)
class Meta:
field_mappings = {
'result_type': 'type'
}
def create(self, validated_data):
print(validated_data)
def update(self, instance, validated_data):
print(validated_data)
'''
Please run the code below in the InteractiveShell to verify the result
from myapp.serializers import RankSerializer
serializer = RankSerializer(data={"limit":15, "type":"Hello"})
serializer.is_valid()
'''
After running the code in the multi line comments to verify the result here is the snapshot
The workaround I found was to modify the parameter name in the validate method as follows:
def validate(self, data):
data = super(RankSerializer, self).validate(data)
data['result_type'] = self.validate_result_type(self.initial_data.get('type'))
return data
Not sure if this is the cleanest way to do it, but I prefer it over #punter147 answer.
Related
I got a Customer model which is related to a Company model. I'd like to give my factory the possibility to use a given company (if I need several customer from the same company). I thought to use the inner class Params to achieve that, but I got an issue using LazyAttribute and SubFactory together. Here is my factory:
class CustomerFactory(UserFactory):
"""
To be valid, customer needs to belong to one of these groups:
- manager
- regular
This can be achieved using the `set_group` parameter.
Example:
manager = CustomerFactory(set_group=manager_group)
"""
#lazy_attribute
def _company(self):
if self.use_company:
return self.use_company
else:
return SubFactory('rdt.tests.factories.CompanyFactory')
class Meta:
model = Customer
class Params:
use_company = None
#post_generation
def set_group(self, create, extracted, **kwargs):
if extracted:
self.groups.add(extracted)
I thought to use the factory as:
c1 = factories.CustomerFactory(use_company=my_company)
c2 = factories.CustomerFactory()
I got ValueError. It seems I can't get the parameter value 'use_company' in the factory.
Anyway my factory throws a ValueError.
I found a solution simply using an other post_generation method like this:
#post_generation
def set_group(self, create, extracted, **kwargs):
if extracted:
self.groups.add(extracted)
#post_generation
def company(self, create, extracted, **kwargs):
if extracted:
self._company = extracted
self.save()
I have a HyperlinkedModelSerializer set up with a few references to other models using the HyperlinkedRelatedField. This works just fine:
class LicenseSerializer(serializers.HyperlinkedModelSerializer):
style = serializers.HyperlinkedRelatedField(
view_name='styles-detail',
queryset=Style.objects.all()
)
order = serializers.HyperlinkedRelatedField(
view_name='orders-detail',
queryset=Order.objects.all()
)
But I need to do some data transformation on some of the other fields, so I override to_internal_value:
def to_internal_value(self, data):
return data
At which point the HyperlinkedRelatedField no long works. I get an error saying:
Cannot assign "'[hyperlinked identity url]'": "[Model.attribute]" must be a "[hyperlinked model]" instance.
Which, at least to me, suggests the HyperlinkedRelatedField no longer works?
Here's a dump of the Serializer when it crashes:
LicenseAppSerializer(context={'request': <rest_framework.request.Request object at 0x03E9D870>}, data={'price': 0, 'style': u'http://localhost:8000/api/1/styles/69/', 'years': 30, 'order': 'http://localhost:8000/api/1/orders/44/', 'qty': 80}):
url = HyperlinkedIdentityField(view_name='licenseapp-detail')
start = DateField(allow_null=True, required=False)
end = DateField(allow_null=True, required=False)
price = DecimalField(decimal_places=2, max_digits=10)
years = IntegerField(allow_null=True, required=False)
qty = IntegerField()
order = HyperlinkedRelatedField(queryset=Order.objects.all(), view_name='order-detail')
style = HyperlinkedRelatedField(queryset=Style.objects.all(), view_name='style-detail')
What am I doing wrong here?
Thanks for your time!
You cannot do this:
def to_internal_value(self, data):
return data
Serializer.to_internal_value validates the received data (data) and converts it into types that are useful on the ORM level.
For HyperLinkedRelatedFields it retrieves (via HyperLinkedRelatedField.to_internal_value) the object that is being linked to. In your case, you directly passed unvalidated data which contained the URLs instead of objects. This might have worked if data contained only primitive types, but would still be insecure.
If you are about to do any transformations, retrieve the objects first:
def to_internal_value(self, data):
validated_data = super().to_internal_value(data)
# Your transformations on validated_data
return validated_data
I made a custom manager that has to randomize my query:
class RandomManager(models.Manager):
def randomize(self):
count = self.aggregate(count=Count('id'))['count']
random_index = random.randint(0, count - 1)
return self.all()[random_index]
When I use the method defined in my manager in the first place, it's works ok:
>>> PostPages.random_objects.randomize()
>>> <PostPages: post 3>
I need to randomize the already filtered query. When I tried to use the manager and the method in chain I got an error:
PostPages.random_objects.filter(image_gallary__isnull=False).randomize()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/home/i159/workspace/shivaroot/shivablog/<ipython-input-9-98f654c77896> in <module>()
----> 1 PostPages.random_objects.filter(image_gallary__isnull=False).randomize()
AttributeError: 'QuerySet' object has no attribute 'randomize'
Result of filtering is not an instance of model class, but it's django.db.models.query.QuerySet, so that it does not have my manager and method, respectively.
Is there a way to use custom manager in chain query?
This is how you chain custom methods on custom manager ie: Post.objects.by_author(user=request.user).published()
from django.db.models.query import QuerySet
class PostMixin(object):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class PostQuerySet(QuerySet, PostMixin):
pass
class PostManager(models.Manager, PostMixin):
def get_query_set(self):
return PostQuerySet(self.model, using=self._db)
Just a code example using the new as_manager() method (see update information from #zzart.
class MyQuerySet(models.query.QuerySet):
def randomize(self):
count = self.aggregate(count=Count('id'))['count']
random_index = random.randint(0, count - 1)
return self.all()[random_index]
class MyModel(models.Model):
.....
.....
objects = MyQuerySet.as_manager()
.....
.....
And then you will be able to use something like this in your code:
MyModel.objects.filter(age__gt=16).randomize()
As you can see, the new as_manager() is really neat:)
Looks like this snippet provides a solution to your situation: Custom managers with chainable filters.
Given that you have an existing models.Manager and you don't want to expose some of the manager method to a chainable queryset, you can use Manager.from_queryset(QuerySet)().
So, you could still place all your chainable queryset method inside the QuerySet and your manager method independently.
Example given in the official site.
Snippet from Django Docs
class BaseManager(models.Manager):
# Available only on Manager.
def manager_only_method(self):
return
class CustomQuerySet(models.QuerySet):
# Available on both Manager and QuerySet.
def manager_and_queryset_method(self):
return
# Available only on QuerySet.
def _private_method(self):
return
CustomManager = BaseManager.from_queryset(CustomQuerySet)
class MyModel(models.Model):
objects = CustomManager()
How about something like below which creates the custom QuerySet dynamically and allows us to 'transplant' our custom queries onto the returned QuerySet instance:
class OfferManager(models.Manager):
"""
Additional methods / constants to Offer's objects manager
"""
### Model (db table) wide constants - we put these and
### not in model definition to avoid circular imports.
### One can access these constants through like
<foo>.objects.STATUS_DISABLED or ImageManager.STATUS_DISABLED
STATUS_DISABLED = 0
...
STATUS_CHOICES = (
(STATUS_DISABLED, "Disabled"),
(STATUS_ENABLED, "Enabled"),
(STATUS_NEGOTIATED, "Negotiated"),
(STATUS_ARCHIVED, "Archived"),
)
...
# we keep status and filters naming a little different as
# it is not one-to-one mapping in all situations
QUERYSET_PUBLIC_KWARGS = {'status__gte': STATUS_ENABLED}
QUERYSET_ACTIVE_KWARGS = {'status': STATUS_ENABLED}
def get_query_set(self):
""" our customized method which transpalats manager methods
as per get_query_set.<method_name> = <method> definitions """
CustomizedQuerySet = QuerySet
for name, function in self.get_query_set.__dict__.items():
setattr(CustomizedQuerySet, name, function)
return CustomizedQuerySet(self.model, using=self._db)
def public(self):
""" Returns all entries accessible through front end site"""
return self.all().filter(**OfferManager.QUERYSET_PUBLIC_KWARGS)
get_query_set.public = public # will tranplat the function onto the
# returned QuerySet instance which
# means 'self' changes depending on context.
def active(self):
""" returns offers that are open to negotiation """
return self.public().filter(**OfferManager.QUERYSET_ACTIVE_KWARGS)
get_query_set.active = active
...
More polished version of this method and django ticket here: https://code.djangoproject.com/ticket/20625.
So I'm trying to return a JSON object for a project. I've spent a few hours trying to get Django just returning the JSON.
Heres the view that we've been working with:
def json(request, first_name):
user = User.objects.all()
#user = User.objects.all().values()
result = simplejson.dumps(user, default=json_util.default)
return HttpResponse(result)
Here's my model:
class User(Document):
gender = StringField( choices=['male', 'female', 'Unknown'])
age = IntField()
email = EmailField()
display_name = StringField(max_length=50)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
location = StringField(max_length=50)
status = StringField(max_length=50)
hideStatus = BooleanField()
photos = ListField(EmbeddedDocumentField('Photo'))
profile =ListField(EmbeddedDocumentField('ProfileItem'))
allProfile = ListField(EmbeddedDocumentField('ProfileItem')) #only return for your own profile
This is what it's returning:
[<User: User object>, <User: User object>] is not JSON serializable
Any thoughts on how I can just return the JSON?
With MongoEngine 0.8 or greater, objects and querysets have a to_json() method.
>>> User.objects.to_json()
simplejson.dumps() doesn't know how to "reach into" your custom objects; the default function, json_util.default must just be calling str() or repr() on your documents. (Is json_util custom code you've written? If so, showing its source here could prove my claim.)
Ultimately, your default function will need to be able to make sense of the MongoEngine documents. I can think of at least two ways that this might be implemented:
Write a custom default function that works for all MongoEngine documents by introspecting their _fields attribute (though note that the leading underscore means that this is part of the private API/implementation detail of MongoEngine and may be subject to change in future versions)
Have each of your documents implement a as_dict method which returns a dictionary representation of the object. This would work similarly to the to_mongo method provided on documents by MongoEngine, but shouldn't return the _types or _cls fields (again, these are implementation details of MongoEngine).
I'd suggest you go with option #2: the code will be cleaner and easier to read, better encapsulated, and won't require using any private APIs.
As dcrosta suggested you can do something like this, hope that will help you.
Document definition
class MyDocument(Document):
# Your document definition
def to_dict(self):
return mongo_to_dict_helper(self)
helper.py:
from mongoengine import StringField, ListField, IntField, FloatField
def mongo_to_dict_helper(obj):
return_data = []
for field_name in obj._fields:
if field_name in ("id",):
continue
data = obj._data[field_name]
if isinstance(obj._fields[field_name], StringField):
return_data.append((field_name, str(data)))
elif isinstance(obj._fields[field_name], FloatField):
return_data.append((field_name, float(data)))
elif isinstance(obj._fields[field_name], IntField):
return_data.append((field_name, int(data)))
elif isinstance(obj._fields[field_name], ListField):
return_data.append((field_name, data))
else:
# You can define your logic for returning elements
return dict(return_data)
I've defined a User class which (ultimately) inherits from models.Model. I want to get a list of all the fields defined for this model. For example, phone_number = CharField(max_length=20). Basically, I want to retrieve anything that inherits from the Field class.
I thought I'd be able to retrieve these by taking advantage of inspect.getmembers(model), but the list it returns doesn't contain any of these fields. It looks like Django has already gotten a hold of the class and added all its magic attributes and stripped out what's actually been defined. So... how can I get these fields? They probably have a function for retrieving them for their own internal purposes?
Django versions 1.8 and later:
You should use get_fields():
[f.name for f in MyModel._meta.get_fields()]
The get_all_field_names() method is deprecated starting from Django
1.8 and will be removed in 1.10.
The documentation page linked above provides a fully backwards-compatible implementation of get_all_field_names(), but for most purposes the previous example should work just fine.
Django versions before 1.8:
model._meta.get_all_field_names()
That should do the trick.
That requires an actual model instance. If all you have is a subclass of django.db.models.Model, then you should call myproject.myapp.models.MyModel._meta.get_all_field_names()
As most of answers are outdated I'll try to update you on Django 2.2
Here posts- your app (posts, blog, shop, etc.)
1) From model link: https://docs.djangoproject.com/en/stable/ref/models/meta/
from posts.model import BlogPost
all_fields = BlogPost._meta.fields
#or
all_fields = BlogPost._meta.get_fields()
Note that:
all_fields=BlogPost._meta.get_fields()
Will also get some relationships, which, for ex: you can not display in a view.
As in my case:
Organisation._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
and
Organisation._meta.get_fields()
(<ManyToOneRel: crm.activity>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
2) From instance
from posts.model import BlogPost
bp = BlogPost()
all_fields = bp._meta.fields
3) From parent model
Let's suppose that we have Post as the parent model and you want to see all the fields in a list, and have the parent fields to be read-only in Edit mode.
from django.contrib import admin
from posts.model import BlogPost
#admin.register(BlogPost)
class BlogPost(admin.ModelAdmin):
all_fields = [f.name for f in Organisation._meta.fields]
parent_fields = BlogPost.get_deferred_fields(BlogPost)
list_display = all_fields
read_only = parent_fields
The get_all_related_fields() method mentioned herein has been deprecated in 1.8. From now on it's get_fields().
>> from django.contrib.auth.models import User
>> User._meta.get_fields()
I find adding this to django models quite helpful:
def __iter__(self):
for field_name in self._meta.get_all_field_names():
value = getattr(self, field_name, None)
yield (field_name, value)
This lets you do:
for field, val in object:
print field, val
This does the trick. I only test it in Django 1.7.
your_fields = YourModel._meta.local_fields
your_field_names = [f.name for f in your_fields]
Model._meta.local_fields does not contain many-to-many fields. You should get them using Model._meta.local_many_to_many.
It is not clear whether you have an instance of the class or the class itself and trying to retrieve the fields, but either way, consider the following code
Using an instance
instance = User.objects.get(username="foo")
instance.__dict__ # returns a dictionary with all fields and their values
instance.__dict__.keys() # returns a dictionary with all fields
list(instance.__dict__.keys()) # returns list with all fields
Using a class
User._meta.__dict__.get("fields") # returns the fields
# to get the field names consider looping over the fields and calling __str__()
for field in User._meta.__dict__.get("fields"):
field.__str__() # e.g. 'auth.User.id'
def __iter__(self):
field_names = [f.name for f in self._meta.fields]
for field_name in field_names:
value = getattr(self, field_name, None)
yield (field_name, value)
This worked for me in django==1.11.8
A detail not mentioned by others:
[f.name for f in MyModel._meta.get_fields()]
get, for example
['id', 'name', 'occupation']
and
[f.get_attname() for f in MyModel._meta.get_fields()]
get
['id', 'name', 'occupation_id']
If
reg = MyModel.objects.first()
then
reg.occupation
get, for example
<Occupation: Dev>
and
reg.occupation_id
get
1
MyModel._meta.get_all_field_names() was deprecated several versions back and removed in Django 1.10.
Here's the backwards-compatible suggestion from the docs:
from itertools import chain
list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in MyModel._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)))
Just to add, I am using self object, this worked for me:
[f.name for f in self.model._meta.get_fields()]
At least with Django 1.9.9 -- the version I'm currently using --, note that .get_fields() actually also "considers" any foreign model as a field, which may be problematic. Say you have:
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
It follows that
>>> map(lambda field:field.name, Parent._model._meta.get_fields())
['id', 'child']
while, as shown by #Rockallite
>>> map(lambda field:field.name, Parent._model._meta.local_fields)
['id']
So before I found this post, I successfully found this to work.
Model._meta.fields
It works equally as
Model._meta.get_fields()
I'm not sure what the difference is in the results, if there is one. I ran this loop and got the same output.
for field in Model._meta.fields:
print(field.name)
In sometimes we need the db columns as well:
def get_db_field_names(instance):
your_fields = instance._meta.local_fields
db_field_names=[f.name+'_id' if f.related_model is not None else f.name for f in your_fields]
model_field_names = [f.name for f in your_fields]
return db_field_names,model_field_names
Call the method to get the fields:
db_field_names,model_field_names=get_db_field_names(Mymodel)
Combined multiple answers of the given thread (thanks!) and came up with the following generic solution:
class ReadOnlyBaseModelAdmin(ModelAdmin):
def has_add_permission(self, request):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def get_readonly_fields(self, request, obj=None):
return [f.name for f in self.model._meta.get_fields()]
Why not just use that:
manage.py inspectdb
Example output:
class GuardianUserobjectpermission(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
object_pk = models.CharField(max_length=255)
content_type = models.ForeignKey(DjangoContentType, models.DO_NOTHING)
permission = models.ForeignKey(AuthPermission, models.DO_NOTHING)
user = models.ForeignKey(CustomUsers, models.DO_NOTHING)
class Meta:
managed = False
db_table = 'guardian_userobjectpermission'
unique_together = (('user', 'permission', 'object_pk'),)