Access a serializer via a variable in drf - django

In the create method in my serializer I'm trying to send a portion of validated data to another serializer as below:
new_serializer = NewSerializer(validated_data.get('customer'))
new_serializer.save()
But in the place of NewSerializer I want to be able to use a variable. I have a dictionary where each model is mapped to its serializer.
my_dict = {"Model1": "Serializer1", "Model2": "Serializer2"}
The model name is available to me and I will use that to find out the corresponding serializer from the dictionary. I have over fifty serializers and I may have to use any one so importing all of them will not be feasible. I want to do something like this:
the_serializer = mydict.get('Model1')
new_serializer = the_serializer(validated_data.get('customer'))
Is there a way to achieve this?

From your question, I interpreted that you want to instantiate a class where the classname is provided by a variable.
For doing so, you can use getattr, however, you need to know in which module the class to be instantiated is located.
For instance, if your serializer class is defined in a module foo.py, you can do something like:
import foo
from yourapp.serializers import DefaultSerializer
the_serializer = "NewSerializer"
SerializerClass = getattr(foo, the_serializer, DefaultSerializer)
args = ("args", "to", "your", "serializer")
serializer_object = SerializerClass(*args)
Or in case you need to handle erronous serializer names:
try:
SerializerClass = getattr(foo, the_serializer, DefaultSerializer)
args = ("args", "to", "your", "serializer")
serializer_object = SerializerClass(*args)
except AttributeError as ae:
print(f"{ae}")
# handle exception here
However, this approach of instantiating the class name doesn't appear appealing to me. There has to be some better way to do what you're trying to do.

I found a way to do this by using import_string in django. I had my app_name and model_name present which were used to import the serializer. All of my serializers are named in {model_name}Serializer format so this way worked for me.
serializer_name = import_string(f"{app_name}.serializers {model_name}Serializer")
ins = serializer_name(data = main_data)
if ins.is_valid():
ins.save()
Here's the django documentation link.
https://docs.djangoproject.com/en/4.0/ref/utils/?fbclid=IwAR22qaDgk0xT9nOY0lP4s3tVh2aKEleefFWQf_L6th5I0DQ56RUMpUmskpo#module-django.utils.module_loading

Related

Filter by URL Kwargs while using Django FilterSets

I have an endpoint that can follow this format:
www.example.com/ModelA/2/ModelB/5/ModelC?word=hello
Model C has a FK to B, which has a FK to A. I should only ever see C's that correspond to the same A and B at one time. In the above example, we should...
filter C by those with an FK to B id = 5 and A id = 2
also filter C by the field 'word' that contains hello.
I know how to use the filter_queryset() method to accomplish #1:
class BaseModelCViewSet(GenericViewSet):
queryset = ModelC.objects.all()
class ModelCViewSet(BaseModelCViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin):
def filter_queryset(self, queryset):
return queryset.filter(ModelB=self.kwargs["ModelB"], ModelB__ModelA=self.kwargs["ModelA"])
I also know how to use a Filterset class to filter by fields on ModelC to accomplish #2
class ModelCFilterSet(GeoFilterSet):
word = CharFilter(field_name='word', lookup_expr='icontains')
But I can only get one or the other to work. If I add filterset_class = ModelCFilterSet to ModelCViewSet then it no longer does #1, and without it, it does not do #2.
How do I accomplish both? Ideally I want all of this in the ModelCFilterSet
Note - As hinted by the use of GeoFilterSet I will (later on) be using DRF to add a GIS query, this is just a simplified example. So I think that restricts me to using FilterSet classes in some manner.
I'm not sure this would be of help in your situation, but I often use nested urls in DRF in a way that would be convenient to perform the task. I use a library called drf-nested-routers that does part of the job, namely keeps track of the relations by the provided ids. Let me show an example:
# views.py
from rest_framework import exceptions, viewsets
class ModelBViewSet(viewsets.ModelViewSet):
# This is a viewset for the nested part that depends on ModelA
queryset = ModelB.objects.order_by('id').select_related('model_a_fk_field')
serializer_class = ModelBSerializer
filterset_class = ModelBFilterSet # more about it below
def get_queryset(self, *args, **kwargs):
model_a_entry_id = self.kwargs.get('model_a_pk')
model_a_entry = ModelA.objects.filter(id=model_a_entry_id).first()
if not model_a_entry:
raise exceptions.NotFound("MAYDAY")
return self.queryset.filter(model_c_fk_field=model_a_entry)
class ModelAViewSet(viewsets.ModelViewSet):
queryset = ModelA.objects.order_by('id')
serializer_class = ModelASerializer
# urls.py
from rest_framework_nested import routers
router = routers.SimpleRouter()
router.register('model-a', ModelAViewSet, basename='model_a')
model_a_router = routers.NestedSimpleRouter(router, 'model-a', lookup='model_a')
model_a_router.register('model-b', ModelBViewSet, basename='model_b')
...
In this case I can make a query like www.example.com/ModelA/2/ModelB/ that will only return the entries of ModelB that point to the object of ModelA with id 2. Likewise, www.example.com/ModelA/2/ModelB/5 will return only the corresponding object of ModelB in case it is related to ModelA-id2. A further level for ModelC would act correspondingly.
Sticking to the example, by now we have filtered the entries of ModelB related to a particular object of ModelA, that is we have received the relevant queryset. Next, we have to search for a particular subset within this queryset, and here's where FilterSet comes in play. The easiest way to customise its behaviour is by writing specific methods.
# filters.py
import django_filters
class ModelBFilterSet(django_filters.FilterSet):
word = django_filters.CharFilter(
method="get_word",
)
def get_word(self, queryset, name, value):
return queryset.filter(word__icontains=value)
In fact, you don't even have to use a method here; the way you pasted would work as well (word = CharFilter(field_name='word', lookup_expr='icontains')), I just wanted to point out that there is such an option too.
The filter starts its job with the queryset that has already been processed by the viewset and now it will just narrow down our sample using the given parameter.
I haven't tried this with a three-level nested URL, only checked on the example of two levels, but I think the third level should act in the same way.
Figured it out. So creating a filter_queryset() method overwrites the one that is in the GenericAPIView class (which I already knew).
However - that overwritten class is also responsible for using the FilterSet class that I defined. So by overwriting it, I also "broke" the FilterSet.
Solution was adding a super() to call the original class before the one I wrote:
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
return queryset.filter(asset=self.kwargs["asset"], asset__program=self.kwargs["program"])

How to make a str var callable

What i want is to make a string var callable. I just have a list with different models_names and i want to call their create methods like this way.
class Object_model_a:
#def ...
class Object_model_b:
#def ...
class Object_model_c:
#def ...
list = ('Object_model_a', 'Object_model_b', 'Object_model_c')
x = list[0]() # this must create a new instance of Object_model_a
This is possible to develop using php like this way:
$hi = 'Hello'
$Hello = 'Hi!!'
echo $$hi
>> Hi!!
Anyone knows if this is possible using django?? This will simplify a lot my code.
Thanks a lot!
You could use Django's get_model helper function, which takes the app name and model name and returns the model class.
from django.db.models import get_model
list = ('Object_model_a', 'Object_model_b', 'Object_model_c')
model = get_model('my_app_name', list[0]) # returns Object_model_a class
instance = model() # creates a model instance
If for whatever reason you have a string that represents a particular class, the correct way to use that to instantiate an object of that class is to place your classes in a dict, keyed by the strings, then just invoke the class.
classes = dict((c.__name__, c) for c in (Object_model_a, Object_model_b, Object_model_c))
instance = classes['Object_model_a']()
Obviously, you don't need to use the classname as the key.
Thanks for all your comments, this is the way i've solved this requirement.
def create_object_instance(model_name, id=None):
for model in get_models():
if str(model.__name__).lower() == str(model_name).lower():
if id:
try:
return model.objects.get(id=id)
except ObjectsDoesNotExist:
return None
else:
return model.objects.all()
return None

In django how can i create a model instance from a string value?

All,
I have strings that represent my model and fields, like this
modelNameStr = 'MyModel'
fieldNameStr = 'modelField'
My model looks like this;
class MyModel(models.Model):
modelField = ForeignKey( ForeignModel )
...
What i want to do is create an instance of MyModel using the string variables, something like
model_instance = modelNameStr.objects.filter(fieldNameStr=ForeignModelInstance)
How can i do this?
Gath
model_instance = ContentType.objects.get(app_label=u'someapp', model=modelNameStr).model_class()(**{fieldNameStr: ForeignModelInstance})
Phew! Try saying that five times fast! But make sure you use the appropriate value for app_label.
Retrieving the model class you can use the get_model function from Django. Though you have to use a string like my_app.MyModel where 'my_app' is your django app which includes the model. Filtering field values can be achieved via a dict. Here an example:
from django.db.models import get_model
modelNameStr = 'my_app.MyModel'
fieldNameStr = 'modelField'
ModelClass = get_model(*model_class.split('.'))
filters = {fieldNameStr: ForeignModelInstance}
model_instance = ModelClass.objects.filter(**filters)

Can't Return JSON object using MongoEngine Pymongo with Django?

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)

Is it possible to specify a QuerySet model dynamically as a string?

I am trying to build a query in Django dynamically. I have a lot of models that I would like to build a query for, but I don't want to code the name of the model, I want to pass it as a string.
from django.db.models.query import QuerySet
a_works = QuerySet(model_A)
a_doesnt_work = QuerySet("model_A") # I want this to work, too
a_works.filter(pk=23) # no error
a_doesnt_work.filter(pk=23) # error: AttributeError: 'str' object has no attribute '_meta'
# then I am dynamically filtering different fields, which works fine with a_works
kwargs = { "%s__%s" % (field, oper) : val }
results = a_works.filter( **kwargs )
Is there a way to make the dynamic model selection work?
Don't try and build querysets via the QuerySet class itself. You should always go via a model's Manager.
You can get the model via the get_model function defined in django.db.models. It takes parameters of the app name and the model name.
from django.db.models import get_model
model = get_model('myapp', 'modelA')
model.objects.filter(**kwargs)
Refer this: https://stackoverflow.com/a/75168880/7212249
from django.apps import apps
def get_app_label_and_model_name(instance: object):
"""
get_model(), which takes two pieces of information — an “app label” and “model name” — and returns the model
which matches them.
#return: None / Model
"""
app_label = instance._meta.app_label
model_name = instance.__class__.__name__
model = apps.get_model(app_label, model_name)
return model
How to use?
model_name = get_app_label_and_model_name(pass_model_object_here)
and use this to get dynamic model name for queries
model_name = get_app_label_and_model_name(pass_model_object_here)
query_set = model_name.objects.filter() # or anything else