Python Marshmallow & SQL-Alchemy - update/store complex objects - flask

Hello community!
So I a a student and relatively new to python, implementing a CRUD-API right now, based on SQL-Alchemy, restx and Flask. For the conversion of the database-objects, I use Marshmallow. The database is a legacy database which can not be changed. In the database, there are some attributes that are encoded, for example a list of labs is encoded as string with the lab names in it. I use converters to cover them, as shown here:
gene_bp = Blueprint('gene', __name__)
api = createApi(gene_bp)
class FooDto(SQLAlchemyAutoSchema):
class Meta:
model = Foo
include_fk = True
include_relationships = True
load_instance = True
bar = decimal_converter.DecimalEncoder()
The Endpoint does look like this:
#api.route('/api/<string:id>')
#api.doc()
class FooRoute(Resource):
def get(self, id):
foo = get_foo_or_abort(id)
if foo:
return createSuccessResponse(FooDto().dump(foo))
The reading-part of the API is no problem with this, but now I struggle with the write/update part. This is my code until now:
#api.route('/api/<string:id>')
#api.doc()
class FooRoute(Resource):
#api.doc(params={'dto': 'Item to update', 'id': 'object id'})
def put(self, id):
fooToStore: FooDto = api.payload
if not fooToStore:
abort(400)
if fooToStore['id'] != id:
abort(400)
# todo: payload to database object
# todo: save payload to database
db.session.commit()
return updatedFoo
So as you can see, there are two ToDos open:
How do i convert the DTO back to a database object, without manual mapping? I understand that there is this method
fooToUpdate = dto.load(geneToStore)
but how do I add the needed complexity for encoded attributes like the lab list, or foreign keys to it?
What is the best practice in storing the data to the database?
Thank you all very much!

Related

Alter the parameter name during serialization

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.

How to pass non-form data in django form object?

I have a data dictionary and want to validate and clean that data to store in database table as defined in models.py
My approach was creating a form class then create an instance of that form class and pass data dictionary instead of request.POST in the view.py.
But this is not working. No error, but cleaned_data returning an empty dictionary.
data = {
'story_title': i.title.text,
'story_source': Sources.objects.get(source_url= source_url),
'pub_date': i.pubDate.text[5:16],
'body_text': des,
'url': i.link.text
}
story_form = StoryForm(data)
if story_form.is_valid():
story_data = story_form.cleaned_data
new_story = Stories(
story_title = story_data['story_title'],
story_source = story_data['story_source'],
pub_date = story_data['pub_date'],
body_text = story_data['body_text'],
url = story_data['url']
)
new_story.save()
I am not confirm that my approach is right or wrong.
While searching for solution I came across HttpRequest.body in Django docs which only says that HttpRequest.body is used to populate form instance with the non-form data / raw data (nothing about how to use).

update django choice field with database results

I am developing an application using django where the UI needs to be updated when user interacts with it. For instance I have a Drop down field where the user selects a drink and submits it then based on that a dropdown with the places that drink is available, price and quantity at each place needs to be displayed. The user will then further submit the form for second process.
From my understanding the Forms in django are pre-defined and I am not able to think of a way using which I could achieve this.
What I could come up was defining a regular form class
class dform(forms.Form):
SOURCES_CHOICES = (
(A, 'A'),
(E, 'E'),
)
drink = forms.ChoiceField(choices = SOURCES_CHOICES)
location = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
quantity = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
.
.
.
My view is like,
def getdrink():
if request.method == 'POST':
#code for handling form
drink = dform.cleaned_data['drink']
#code to get values from database
I have no idea how to generate or populate or append the values i get from the database to the choicefield in my form. I did try looking up on SO but none of the solutions here explained properly how to do it. Also, due to certain requirements I am not using the models. So my database is not at all related to the models.
I am at a total loss Please help me out
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=MY_CHOICES)
So if you want the values to be dynamic(or dependent of some logic) you can simply modify your code to something like this:
either
def get_my_choices():
# you place some logic here
return choices_list
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
or
User_list = [ #place logic here]
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
but once database value is updated, new data value will be popoulated only on restart of server.
So write a function like this in forms:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_choice_field'] = forms.ChoiceField( choices=get_my_choices() )
or in place of the get_my_choices u can ad the USER_LIST too.
If you have models for location and quantity, a ModelChoiceField should work:
class dform(forms.Form):
location = forms.ModelChoiceField(queryset = Location.objects.all())
Otherwise, you'll need to query the database directly, for example:
class dform(forms.Form):
location = forms.ChoiceField(choices = get_location_choices())
# elsewhere
from django.db import connection
def get_location_choices():
cursor = connection.cursor()
cursor.execute("select location_id, name from location_table")
return cursor.fetchall()
The SQL query to use here depends on your database engine and table schema.
I think that, based on my understanding of your question, the best solution would be to include JSON objects with your form and load these using jQuery instead of submitting the form over and over. Included in your form, you should add something like:
class MyForm(forms.Form):
CHOICE_DICT = {
'choice_1': [
'option_1',
'option_2',
],
etc...
Then you should include form.CHOICE_DICT in your context, load that with jQuery, and render it depending on changes to other fields.

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)

How can I use Flex to access foreign-keyed fields in Django?

I have the following Django and Flex code:
Django
class Author(models.Model):
name = models.CharField(max_length=30)
class Book(models.Model):
title = models.CharField(max_length=30)
author = models.ForeignKeyField(Author)
Flex
package com.myproject.models.vo
{
[Bindable]
[RemoteClass(alias="myproject.models.Book")]
public class BookVO
{
public var id:int;
public var title:String;
public var author: AuthorVO;
}
}
As you can see in this example, Author is a foreign key in my Book model. Now I'd like to acccess the author's name when I call my BookVO in Flex. As such, I'd expect code like the following to work, but "author_name" results in a null:
var book = new BookVO();
var author_name = book.author.name;
I realize I could call an AuthorVO directly, but the crux of this question is how can you retrieve foreign-keyed values using Flex when your VOs are bound to a remote object? I'm currently using PyAMF to bridge the gap between Flex and Django, but I'm not sure that's relevant.
Ok, Here's an example...
Model:
class Logger(models.Model):
lname = models.CharField(max_length=80)
def __unicode__(self):
return self.lname
#
#
class DataSource(models.Model):
dsname = models.CharField(max_length=80)
def __unicode__(self):
return self.dsname
#
#
class LoggedEvent(models.Model):
# who's data is this?
who = models.ForeignKey(Logger)
# what source?
source = models.ForeignKey(DataSource)
# the day (and, for some events also the time)
when = models.DateTimeField()
# the textual description of the event, often the raw data
what = models.CharField(max_length=200)
# from -1.0 to 1.0 this is the relative
# importance of the event
weight = models.FloatField()
def __unicode__(self):
return u"%2.2f %s:%s - %s" % (self.weight, self.source, self.who, self.what)
#
#
Here's my amfgateway.py
def fetch_events(request, source):
events = LoggedEvent.objects.select_related().all()
return events
#
services = {
'recall.fetch_events': fetch_events,
}
gateway = DjangoGateway(services)
and here's my Actionscript for the receiving side of the AMF call:
protected function onRetrievedEvents(result: Object): void {
for each(var evt: Object in result) {
var who: Object = evt._who_cache.lname;
...
The evt._who_cache.lname is populated with the select_related() and missing when the select related is missing. If I get rid of the select_related() call, then I see the error:
TypeError: Error #1010: A term is undefined and has no properties.
You must be trying a different technique with your RemoteClass... so the select_related might not be the problem at all... (otherwise my first answer wouldn't have gotten negged.) The rest is up to you.
when you get your book from the database, try using select_related()
which is way down on this page:
http://docs.djangoproject.com/en/dev/ref/models/querysets/
it, "will automatically "follow" foreign-key relationships, selecting that additional related-object data when it executes its query. This is a performance booster which results in (sometimes much) larger queries but means later use of foreign-key relationships won't require database queries."
I've been loving how seamless the access to the database is through PyAMF from Flex. It's really brilliant.