Graphene Django without Django Model? - python

I've successfully used Graphene-Django to successfully build several GraphQL calls. In all of those cases I populated, in whole or in part, a Django model and then returned the records I populated.
Now I have a situation where I'd like to return some data that I don't wish to store in the Django model. Is this possible to do with Graphene?
Robert

Robert_LY answered his own question perfectly in the comments, I'd just like to expand his solution.
My database-less model WordForm is generated automatically, without storing it in a database. I define it as a Django model as follows:
from django.db import models
class WordForm(models.Model):
value = models.CharField(max_length=100)
attributes = models.CharField(max_length=100)
In the schema I define the node and query like this:
class WordFormNode(DjangoObjectType):
class Meta:
model = WordForm
interfaces = (relay.Node, )
class Query(AbstractType):
word_forms = List(WordFormNode,query=String(),some_id=String())
def resolve_word_forms(self, args, context, info):
query= args['query']
some_id = from_global_id(args['some_id'])[1]
word_forms = []
# some logic to make WordForm objects with
# WordForm(value=value,attributes=attributes),
# then append them to list word_forms
return word_forms
You can pass as many arguments as you like to the List and access them in resolve_word_forms.

When you map your Django model to a GraphQL, it create a new model with GraphQL object types from the introspection of the Django model..
And nothing prevent you to combine this model with with plain GraphQL objects types, or mapped from an other third party persistence model

Related

How to filter database query by context information from request? [duplicate]

I have a model with a field is_deleted, now I want all forms of query for this model to always filter by is_deleted=False in addition to whatever filtering arguments is passed to .filter and .filter_by.
In Django, I would normally override the manager and add my own filtering but I need help for SQLAlchemy.
UPDATE:
I ended-up doing the following:
class CustomQuery(Query):
def __new__(cls, *args, **kwargs):
if args and hasattr(args[0][0], "is_deleted"):
return Query(*args, **kwargs).filter_by(is_deleted=False)
else:
return object.__new__(cls)
session = scoped_session(sessionmaker(query_cls=CustomQuery))
It works but if I have more fields later on I imagine I'll have to add more condition, there must be a way to do this on the model level.
This is a very old question so I'm sure the OP solved their issue, but as it remains unanswered (in 2021) here's how we've approached applying a custom query class to all models:
Define the custom query as above
class CustomQuery(Query): ...
Then set this query class as the query attribute on your base model class:
class BaseModel(Model):
__abstract__ = True
query_class = CustomQuery
...
Then any models implementing the BaseModel will obviously inherit this behaviour:
class MyModel(BaseModel):
__tablename__ = 'my_model'
....
Note, in our case not all of the tables follow the soft delete pattern (we don't care about the history of every single table). Here, you could implement a separate BaseModel that uses the default query class.
class ImmutableBaseModel(Model):
__abstract__ = True
query_class = CustomQuery
...
class MutableBaseModel(Model):
__abstract__ = True
If you find yourself here and you've not read it yet check out this excellent blog post from Miguel Grinberg on implementing the soft delete pattern and accompanying repo
 

Validating a Django model field based on another field's value?

I have a Django app with models accessible by both Django REST Framework and a regular form interface. The form interface has some validation checks before saving changes to the model, but not using any special Django framework, just a simple local change in the view.
I'd like to apply the same validation to forms and REST calls, so I want to move my validation into the model. I can see how to do that for simple cases using the validators field of the Field, but in one case I have a name/type/value model where the acceptable values for 'value' change depending on which type is selected. The validator doesn't get sent any information about the model that the field is in, so it doesn't have access to other fields.
How can I perform this validation, without having essentially the same code in a serializer for DRF and my POST view for the form?
I dug around codebase of drf a little bit. You can get values of all fields using following approach. Doing so, you can throw serialization error as
{'my_field':'error message} instead of {'non_field_error':'error message'}.
def validate_myfield(self, value):
data = self.get_initial() # data for all the fields
#do your validation
However, if you wish to do it for ListSerializer, i.e for serializer = serializer_class(many=True), this won't work. You will get list of empty values.
In that scenario, you could write your validations in def validate function and to avoid non_field_errors in your serialization error, you can raise ValidationError with error message as a dictionary instead of string.
def validate(self, data):
# do your validation
raise serializers.ValidationError({"your_field": "error_message"})
The validation per-field doesn't get sent any information about other fields, when it is defined like this:
def validate_myfield(self, value):
...
However, if you have a method defined like this:
def validate(self, data):
...
Then you get all the data in a dict, and you can do cross-field validation.
You can use the required package for your cross-field validation. It allows you to express your validation rules declaratively in python. You would have something like this with DRF:
class MySerializer(serializers.Serializer):
REQUIREMENTS = (
Requires("end_date", "start_date") +
Requires("end_date", R("end_date") > R("start_date")) +
Requires("end_date", R("end_date") < today.date() + one_year) +
Requires("start_date", R("start_date") < today.date() + one_year)
)
start_date = serializers.DateField(required=False, null=True, blank=True)
end_date = serializers.DateField(required=False, null=True, blank=True)
def validate(self, data):
self.REQUIREMENTS.validate(data) # handle validation error
You could put the REQUIREMENTS on your Model and have both your DRF and Django Form validate your data using it.
Here is a blog post explaining more

How to define a selection field in flask

I want to define a selection field in python, i.e. field that is limited to a set of values. How can I do that in flask framework. I could not find anything on selection fields in the following sources:
Declaring Models
SQLAlchemy in Flask
I am using sqlalchemy for ORM.
I assume you mean a field in a form that has a limited set of options; to do this you can use WTForms and its extensions which allow you to create forms from models.
Once you have done that, you can then limit the choices for a field based on a model condition.
As you haven't posted your model, here is the example give you give you an idea on how this would work:
def enabled_categories():
return Category.query.filter_by(enabled=True)
class BlogPostEdit(Form):
title = TextField()
blog = QuerySelectField(get_label='title')
category = QuerySelectField(query_factory=enabled_categories,
allow_blank=True)
def edit_blog_post(request, id):
post = Post.query.get(id)
form = ArticleEdit(obj=post)
# Since we didn't provide a query_factory for the 'blog' field, we need
# to set a dynamic one in the view.
form.blog.query = Blog.query.filter(Blog.author == request.user) \
.order_by(Blog.name)

Django Rest Framework - Read nested data, write integer

So far I'm extremely happy with Django Rest Framework, which is why I alsmost can't believe there's such a large omission in the codebase. Hopefully someone knows of a way how to support this:
class PinSerializer(serializers.ModelSerializer):
item = ItemSerializer(read_only=True, source='item')
item = serializers.IntegerSerializer(write_only=True)
class Meta:
model = Pin
with the goal
The goal here is to read:
{pin: item: {name: 'a', url: 'b'}}
but to write using an id
{pin: item: 10}
An alternative would be to use two serializers, but that looks like a really ugly solution:
django rest framework model serializers - read nested, write flat
Django lets you access the Item on your Pin with the item attribute, but actually stores the relationship as item_id. You can use this strategy in your serializer to get around the fact that a Python object cannot have two attributes with the same name (a problem you would encounter in your code).
The best way to do this is to use a PrimaryKeyRelatedField with a source argument. This will ensure proper validation gets done, converting "item_id": <id> to "item": <instance> during field validation (immediately before the serializer's validate call). This allows you to manipulate the full object during validate, create, and update methods. Your final code would be:
class PinSerializer(serializers.ModelSerializer):
item = ItemSerializer(read_only=True)
item_id = serializers.PrimaryKeyRelatedField(write_only=True,
source='item',
queryset=Item.objects.all())
class Meta:
model = Pin
fields = ('id', 'item', 'item_id',)
Note 1: I also removed source='item' on the read-field as that was redundant.
Note 2: I actually find it rather unintuitive that Django Rest is set up such that a Pin serializer without an Item serializer specified returns the item_id as "item": <id> and not "item_id": <id>, but that is beside the point.
This method can even be used with forward and reverse "Many" relationships. For example, you can use an array of pin_ids to set all the Pins on an Item with the following code:
class ItemSerializer(serializers.ModelSerializer):
pins = PinSerializer(many=True, read_only=True)
pin_ids = serializers.PrimaryKeyRelatedField(many=True,
write_only=True,
source='pins',
queryset=Pin.objects.all())
class Meta:
model = Item
fields = ('id', 'pins', 'pin_ids',)
Another strategy that I previously recommended is to use an IntegerField to directly set the item_id. Assuming you are using a OneToOneField or ForeignKey to relate your Pin to your Item, you can set item_id to an integer without using the item field at all. This weakens the validation and can result in DB-level errors from constraints being violated. If you want to skip the validation DB call, have a specific need for the ID instead of the object in your validate/create/update code, or need simultaneously writable fields with the same source, this may be better, but I wouldn't recommend anymore. The full line would be:
item_id = serializers.IntegerField(write_only=True)
If you are using DRF 3.0 you can implement the new to_internal_value method to override the item field to change it to a PrimaryKeyRelatedField to allow the flat writes. The to_internal_value takes unvalidated incoming data as input and should return the validated data that will be made available as serializer.validated_data. See the docs: http://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data
So in your case it would be:
class ItemSerializer(ModelSerializer):
class Meta:
model = Item
class PinSerializer(ModelSerializer):
item = ItemSerializer()
# override the nested item field to PrimareKeyRelatedField on writes
def to_internal_value(self, data):
self.fields['item'] = serializers.PrimaryKeyRelatedField(queryset=Item.objects.all())
return super(PinSerializer, self).to_internal_value(data)
class Meta:
model = Pin
Two things to note: The browsable web api will still think that writes will be nested. I'm not sure how to fix that but I only using the web interface for debug so not a big deal. Also, after you write the item returned will have flat item instead of the nested one. To fix that you can add this code to force the reads to use the Item serializer always.
def to_representation(self, obj):
self.fields['item'] = ItemSerializer()
return super(PinSerializer, self).to_representation(obj)
I got the idea from this from Anton Dmitrievsky's answer here: DRF: Simple foreign key assignment with nested serializers?
You can create a Customized Serializer Field (http://www.django-rest-framework.org/api-guide/fields)
The example took from the link:
class ColourField(serializers.WritableField):
"""
Color objects are serialized into "rgb(#, #, #)" notation.
"""
def to_native(self, obj):
return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
def from_native(self, data):
data = data.strip('rgb(').rstrip(')')
red, green, blue = [int(col) for col in data.split(',')]
return Color(red, green, blue)
Then use this field in your serializer class.
I create a Field type that tries to solve the problem of the Data Save requests with its ForeignKey in Integer, and the requests to read data with nested data
This is the class:
class NestedRelatedField(serializers.PrimaryKeyRelatedField):
"""
Model identical to PrimaryKeyRelatedField but its
representation will be nested and its input will
be a primary key.
"""
def __init__(self, **kwargs):
self.pk_field = kwargs.pop('pk_field', None)
self.model = kwargs.pop('model', None)
self.serializer_class = kwargs.pop('serializer_class', None)
super().__init__(**kwargs)
def to_representation(self, data):
pk = super(NestedRelatedField, self).to_representation(data)
try:
return self.serializer_class(self.model.objects.get(pk=pk)).data
except self.model.DoesNotExist:
return None
def to_internal_value(self, data):
return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
And so it would be used:
class PostModelSerializer(serializers.ModelSerializer):
message = NestedRelatedField(
queryset=MessagePrefix.objects.all(),
model=MessagePrefix,
serializer_class=MessagePrefixModelSerializer
)
I hope this helps you.

Returning non-predefined fields via. API with Tastypie in Django

I am using Tastypie for non-ORM data source (Amazon Dynamodb). I have gone through the official documentation for non-ORM source and found the following code:
class MessageResource(Resource):
# Just like a Django ``Form`` or ``Model``, we're defining all the
# fields we're going to handle with the API here.
uuid = fields.CharField(attribute='uuid')
user_uuid = fields.CharField(attribute='user_uuid')
message = fields.CharField(attribute='message')
created = fields.IntegerField(attribute='created')
I am new to Tastypie and what I understand is that fields uuid, message, created.. which are returned by API are defined over here. Is there any way that I return those fields that are not defined here i.e. all those fields returned by the dictionary in obj_get_list or obj_get.
You can use the dehydrade method. Simply add a new key to bundle.data.
def dehydrate(self, bundle):
for item in bundle.obj.iteritems():
bundle.data["new_key"] = "new_value"
return bundle

Categories