flask-marshmallow sqlalchemy cannot dump objects - flask

I have a very simple implementation of Flask, Flask-Restful, Flask-SqlAlchemy, and Flask-Marshmallow:
I am getting the validation error: {'_schema': ['Invalid input type.']} when trying to dump an object I just created.
init.py
app = Flask(__name__)
configure_rds(app)
db = SQLAlchemy(app)
ma = Marshmallow(app)
api = Api(app)
register_blueprints(app)
model.py
class MyModel(db.Model):
__tablename__ = 'my_table'
id = db.Column(db.Integer, primary_key=True)
uid = db.Column(db.String(255))
other_id = db.Column(db.Integer)
last_modified = db.Column(db.Date)
status = db.Column(db.String(255))
Schemas.py
class MyModelSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = MyModel
load_instance = True
id = ma.auto_field()
uid = ma.auto_field()
other_id = ma.auto_field()
last_modified = ma.auto_field()
status = ma.auto_field()
views.py
class MyModelView(Resource):
def get(self):
maps = MyModel.query.all()
ma_schema = MyModelSchema(many=True)
body = ma_schema.dump(maps)
resp = jsonify(body)
resp.status_code = 200
new_map = maps[0]
return resp
def post(self):
args = self.parser.parse_args()
ma_schema = MyModelSchema(many=False)
# new_model = ms.load(args, session=db.session)
# new_model .last_modified = date.today()
new_model = MyModel(uid=args['uid'],
other_id=args['other_id'],
status=args['status'],
last_modified=datetime.date.today())
db.session.add(new_model )
db.session.commit()
body = ma_schema.dump(new_model)
resp = jsonify(body)
resp.status_code = 200
The get method works just fine. The post will add the new model to the database but then fail to serialize the model with the schema. It is also unable to load my reqparse args into the schema as well. Why would it be able to add to the RDS, read from the RDS, and yet not be able to serialize the object?
I have tried a bunch of different column types, tried various setups of the schema, nothing seems to work. Also I have verified that the exact types of the model's attributes in the get match the attributes of the model I am creating and attempting to dump... So I am really unsure of what else it could be.

Related

DRF Invalid pk object does not exist for foreign key on POST create

I have two classes related to each other.
One class I have made the primary key a char field so I can easily reference to it or create it to match the id of the actual object (all objects will have this unique name).
from django.db import models
class Ven(models.Model):
id = models.CharField(max_length=80, primary_key=True)
statusOn = models.BooleanField(default=True)
class Device(models.Model):
device_id = models.CharField(max_length=80)
ven_id = models.ForeignKey(VEN, related_name='devices', on_delete=models.SET_NULL, null=True)
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
class VENSerializer(serializers.ModelSerializer):
class Meta:
model = VEN
fields = ['id', 'statusOn', 'devices']
class DeviceList(generics.ListCreateAPIView):
logger.info("DeviceList: view")
# permission_classes = [permissions.IsAuthenticated]
queryset = Device.objects.all()
serializer_class = DeviceSerializer
however when I try to run my test:
class DevicesTestCase(TestCase):
def setUp(self):
self.factory = Client()
def test_create_device(self):
device = {
"ven_id": "TEST_VEN_1",
"device_id": "TEST_DEVICE_1",
"statusOn": True,
}
response = self.factory.post('/devices/', data=device)
self.assertEqual(response.status_code, 201)
my response returns 400 and states:
b'{"ven_id":["Invalid pk \\"TEST_VEN_1\\" - object does not exist."]}'
I'm trying to write a custom create in my serializer to create the ven if it does not exist but it's not being called. Data is being validated else where. My breakpoint in my view set's perform_create also does not fire.
I don't want to write a bunch of workaround code for something that should be straightforward. I know I'm missing/messing something up somewhere.
I think you need to customize the create method in the DeviceSerializer.
class DeviceSerializer(serializers.ModelSerializer):
ven_id = serializers.CharField()
status_on = serializers.BooleanField(write_only = True)
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
def create(self, validated_data):
ven_id = validated_data.pop('ven_id')
status_on = validated_data.pop('status_on')
try:
ven = Ven.objects.get(id = ven_id)
except Ven.DoesNotExist:
ven = Ven.objects.create(id = ven_id, statusOn = status_on)
new_device = Device.objects.create(ven_id = ven, **validated_data)
return new_device

Iterating over model constraints in bulk data create on Django

I am trying to save data from an API with constant live updates. Hence, the data from the API will always container an existing dataset on my database. So I created a unique_together to prevent duplicate.
The model looks like:
class Autodamb(models.Model, Activity):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='highlight', null=True)
title = models.CharField(max_length=300)
url = models.URLField(null=True)
embed_donotuse = models.CharField(max_length=1000)
date = models.CharField(max_length=300)
thumbnail = models.URLField()
home_team_name = models.CharField(max_length=100)
away_team_name = models.CharField(max_length=100)
class Meta:
unique_together = ["title", "date"]
The views.py:
def save_damb_video(request):
r = requests.get('https://www.brainxo.com/com-api/v1/?apid=xxxxxxxxxxx')
# data = json.loads(r)
data = r.json()
for x in data:
title = x["title"]
embed_donotuse = x["embed"]
url = x["url"]
date = x["date"]
thumbnail = x["thumbnail"]
home_team_name = x["side1"]["name"]
away_team_name = x["side2"]["name"]
user = request.user
damb_data = Autodamb.objects.create(title=title, embed_donotuse=embed_donotuse, url=url, date=date, thumbnail=thumbnail, home_team_name=home_team_name, away_team_name=away_team_name, user=user)
damb_data.save()
return HttpResponse("DAMP API data successfully saved")
The problem is that the entire process would fail with a unique_together as some data already exist. Any Idea how to iterate and only save data not already existing while maintaining the unique constraint?
This will ignore rows that have an already existing combination of title and date and just insert the rest.
objects = []
for x in data:
...
damb_data = Autodamb(
title=title,
embed_donotuse=embed_donotuse,
url=url, date=date,
thumbnail=thumbnail,
home_team_name=home_team_name,
away_team_name=away_team_name,
user=user
)
objects.append(damb_data)
bulk_create(objects, ignore_conflicts=True)
PS. I would recommend check out Django Rest Framework if you're building an API in Django. It has Serializers for just this kind of thing.

How to update django models after importing csv if id already exists using django-import-export?

I am using django-import-export module for importing csv in my application. However what I want is if I upload the same csv again with some vales changed, it updates the existing values in models rather than adding the same values in new rows. Here is my code:
#resources.py:
class ProductResource(resources.ModelResource):
class Meta:
model = Product
import_id_fields = ('p_id',)
skip_unchanged = True
report_skipped = False
#views.py:
if request.method == 'POST' and request.FILES['myfile']:
product_resource = ProductResource()
dataset = Dataset()
new_product = request.FILES['myfile']
imported_data = dataset.load(new_product.read().decode('utf-8'),format='csv')
result = product_resource.import_data(dataset, dry_run = True)
if not result.has_errors():
product_resource.import_data(dataset, dry_run=False)
#models.py:
class Product(models.Model):
p_id = models.CharField(max_length=20)
name = models.CharField(max_length=20)
quantity = models.IntegerField()
prop1 = models.CharField(max_length=30)
prop2 = models.CharField(max_length=30)
def __str__(self):
return self.p_id
You can use exclude=('id') then remove the id from the file.

How to construct an API endpoint with foreign keys replaced by their values?

I am currently building an API using Flask, SQLAlchemy and Marshmallow. I have a fairly simple DB model like so:
class Transaction(db.Model):
#Transaction Model
__tablename__ = 'transactions'
id = db.Column(db.Integer, primary_key = True)
created_at = db.Column(db.DateTime, default=datetime.datetime.now)
date = db.Column(db.DateTime)
description = db.Column(db.Text)
amount = db.Column(db.Float)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
category = db.relationship('Category', uselist=False, lazy='select')
sub_category_id = db.Column(db.Integer, db.ForeignKey('sub_categories.id'))
sub_category = db.relationship('SubCategory', uselist=False, lazy='select')
account = db.Column(db.Integer, db.ForeignKey('accounts.id'))
inner_transfer = db.Column(db.Boolean)
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key = True)
category = db.Column(db.String(64), unique = True)
transaction = db.relationship('Transaction')
My Marshmallow Schema looks as follows:
class CategorySchema(ma.ModelSchema):
class Meta:
model = Category
category_schema = CategorySchema()
categories_schema = CategorySchema(many=True)
class TransactionSchema(ma.ModelSchema):
class Meta:
model = Transaction
include_fk = True
And a very simple API endpoint:
#api.route('/transactions', methods=['GET'])
def transactions():
all_transactions = Transaction.query.all()
for transaction in all_transactions:
transaction.category = Category.query.filter_by(id=transaction.category_id).first()
print(transaction.category)
items, errors = transactions_schema.dump(all_transactions)
return jsonify(items)
My problem now: How do I construct a JSON response that has the category name instead of it's primary key in it? I tried the "for transaction in all transactions" bit and in the print statement I get the category name but it doesn't show in my JSON response...
I'm not entirely sure how to write the Database Model. What I'm particularly stuck with is the db.relationship. Declaring it in my Transaction class doesn't seem to help since it only shows the primary key of the Category class as well. I read the Flask-SQLAlchemy docs up and down, but I'm clueless.
Any help and pointers are greatly appreciated!
Thanks!
I created a simple version of your model to show this in action and seeded the db with two test records:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
ma = Marshmallow(app)
# DB Models
class Transaction(db.Model):
#Transaction Model
__tablename__ = 'transactions'
id = db.Column(db.Integer, primary_key = True)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
category = db.relationship('Category', uselist=False, lazy='select')
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key = True)
category = db.Column(db.String(64), unique = True)
transaction = db.relationship('Transaction')
# Schema
class TransactionSchema(ma.ModelSchema):
class Meta:
model = Transaction
include_fk = True
# Here is the function, you do not not need to define all fields you want to dump
category_name = ma.Function(lambda obj: obj.category.category)
Then calling:
TransactionSchema(many=True).dump(Transaction.query.all()).data
Yields:
[{u'category': 1, u'category_id': 1, u'category_name': u'Test1', u'id': 1},
{u'category': 2, u'category_id': 2, u'category_name': u'Test2', u'id': 2}]
Could also just overwrite your field with the funtion and make it dump only:
class TransactionSchema(ma.ModelSchema):
class Meta:
model = Transaction
include_fk = True
category = ma.Function(lambda obj: obj.category.category, dump_only=True)
Yields:
[{u'category': u'Test1', u'category_id': 1, u'id': 1},
{u'category': u'Test2', u'category_id': 2, u'id': 2}]

Django ORM giving error when trying to use User with additional info

I'm using Django 1.4 with Python 2.7 on Ubuntu 12.04.
Edit: I'm clearing some of the erroneous portions of this question out as it appears the same problem has re-occurred. It was working wonderfully - I made no changes - and now it's failing. What's up with that?
This is basically what the views look like:
#login_required
def request_new_project(request):
"""
.. function:: request_new_project()
Collect information to call the form used to create a new project
:param request: Django Request object
"""
user_dict = { 'rsb_username' : request.user.username}
form = CreateProject(initial = user_dict)
data = { 'user' : request.user }
data.update(csrf(request))
data.update({ 'form' : form })
return render_to_response("create_project.html", data)
#login_required
def add_project(request):
"""
.. function:: add_project()
Add a project for the user
:param request: Django Request object
"""
if (request.method == "POST"):
user = User.objects.get(username = request.POST.get('rsb_username'))
userProfile = UserProfile.objects.get(user = user)
new_project = Projects(client = userProfile,
project_name = request.POST.get('rsb_project_name'),
description = request.POST.get('rsb_description'),
budget = request.POST.get('rsb_budget'),
time_frame = request.POST.get('rsb_time_frame'),
time_frame_units = request.POST.get('rsb_time_frame_units'),
contact = request.POST.get('rsb_point_of_contact'),
contact_email = request.POST.get('rsb_contact_email'),
contact_phone = request.POST.get('rsb_contact_phone'),
price_quote = 0,
eta = 'To Be Determined',
current_status = 'Waiting for quote',
)
new_project.save()
return view_projects(request)
I get the following error:
Cannot assign "<UserProfile: UserProfile object>": "Projects.client" must be a "User" instance.
I didn't change the models.
# Create a table for users
class UserProfile(models.Model):
user = models.OneToOneField(User)
# Client Info
company_name = models.CharField(max_length = 200)
client_type = models.CharField(max_length = 200)
address1 = models.CharField(max_length = 200)
address2 = models.CharField(max_length = 200)
city = models.CharField(max_length = 200)
state = models.CharField(max_length = 200)
country = models.CharField(max_length = 200)
zip_code = models.CharField(max_length = 200)
phone_number = models.CharField(max_length = 200)
# Create a table to manage project requests
class Projects(models.Model):
client = models.ForeignKey(User)
project_name = models.CharField(max_length = 50)
description = models.TextField()
budget = models.CharField(max_length = 50)
time_frame = models.DecimalField(max_digits = 3, decimal_places = 1)
time_frame_units = models.CharField(max_length = 25)
contact = models.CharField(max_length = 50)
contact_email = models.EmailField()
contact_phone = models.CharField(max_length = 25)
price_quote = models.DecimalField(max_digits = 10, decimal_places = 2)
eta = models.CharField(max_length = 200)
current_status = models.CharField(max_length = 200)
Any suggestions?
UPDATE 1:
From the actual database I can see that one of the rsb_projects constraints is:
rsb_projects_client_id_fkey (client_id) REFERENCE rsb_userprofile (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED
If that helps...
It seems to me that even though I've defined the ForeignKey in the models.py to be against User the database wants the UserProfile id.
Thoughts?
The error is exactly what it says it is.
In order to create the new project you need to give your project object a user profile object, not a user profile id.
user = User.objects.get(id=7)
userprofile = user.get_profile()
project.client = userprofile
Notice that it is userprofile object that is assigned to the project instance's client attribute. You cannot assign the userprofile object's id to the project instance's client attribute.
Also, do not confuse your user id with your userprofile id. They may not be exactly the same.
The user id is the primary key auto-generated in the auth_user table whenever a new user is created in your database.
The userprofile id is the primary key auto-generated in the profiles_userprofile table whenever a corresponding user profile is created that is related to a user.
In other words, your add_project view function needs to read something like
if (request.method == "POST"):
user = User.objects.get(username = request.POST.get('rsb_username'))
# userprofile = UserProfile.objects.get(user = user) <-- we don't need this anymore.
new_project = Projects(client = user, # <--- give it a user instance of course now that your model has changed
project_name = request.POST.get('rsb_project_name'),
description = request.POST.get('rsb_description'),
budget = request.POST.get('rsb_budget'),
time_frame = request.POST.get('rsb_time_frame'),
time_frame_units = request.POST.get('rsb_time_frame_units'),
contact = request.POST.get('rsb_point_of_contact'),
contact_email = request.POST.get('rsb_contact_email'),
contact_phone = request.POST.get('rsb_contact_phone'),
)
new_project.save()
The key takeaway is that you need to be sure what your original model definition is.
If in your Project class, your client attribute is assigned as FK to User, then you need to give it a user object.
If in your Project class, your client attribute is assigned as FK to UserProfile, then you need to give it a userprofile object.