How do you use factory_boy to model a MongoEngine EmbeddedDocument? - python

I'm trying to use factory_boy to help generate some MongoEngine documents for my tests. I'm having trouble defining EmbeddedDocumentField objects.
Here's my MongoEngine Document:
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField(), required=True)
comments = ListField(EmbeddedDocumentField(Comment))
Here's my partially completed factory_boy Factory:
class CommentFactory(factory.Factory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
class BlogFactory(factory.Factory):
FACTORY_FOR = Blog
title = "On Using MongoEngine with factory_boy"
tags = ['python', 'mongoengine', 'factory-boy', 'django']
comments = [factory.SubFactory(CommentFactory)] # this doesn't work
Any ideas how to specify the comments field? The problem is that factory-boy attempts to create the Comment EmbeddedDocument.

I'm not sure if this is what you want but I just started looking at this problem and this seems to work:
from mongoengine import EmbeddedDocument, Document, StringField, ListField, EmbeddedDocumentField
import factory
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField(), required=True)
comments = ListField(EmbeddedDocumentField(Comment))
class CommentFactory(factory.Factory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
class PostFactory(factory.Factory):
FACTORY_FOR = Post
title = "On Using MongoEngine with factory_boy"
tags = ['python', 'mongoengine', 'factory-boy', 'django']
comments = factory.LazyAttribute(lambda a: [CommentFactory()])
>>> b = PostFactory()
>>> b.comments[0].content
'Platinum coins worth a trillion dollars are great'
I wouldn't be surprised if I'm missing something though.

The way that I'm doing it right now is to prevent the Factories based on EmbeddedDocuments from building. So, I've setup up an EmbeddedDocumentFactory, like so:
class EmbeddedDocumentFactory(factory.Factory):
ABSTRACT_FACTORY = True
#classmethod
def _prepare(cls, create, **kwargs):
return super(EmbeddedDocumentFactory, cls)._prepare(False, **kwargs)
Then I inherit from that to create factories for EmbeddedDocuments:
class CommentFactory(EmbeddedDocumentFactory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
This may not be the best solution, so I'll wait on someone else to respond before accepting this as the answer.

Related

How to do f string for exec?

This is my Django app name SFC
This is its models.py:
from django.db import models
import time
color = (
('1','果盤'),
('3','港灣'),
('5','輕快感'),
('6','鵝卵石'),
('7','調味料'),
('8','農收'),
('9','開瓶'),
('10','潛水'),
)
# The Chinese in the tuple name color is a noun describe the color.
# Create your models here.
class class_site(models.Model):
school_name = models.CharField("學校英文縮寫",max_length=10,default='CKJH')
Class = models.CharField("班級",max_length=5,default='802')
page_name = models.CharField("網頁名稱",max_length=10,default='八我啊二班')
color = models.CharField('配色',max_length=8,choices=color,default="6")
logo = models.ImageField('網站logo',null=True)
def __str__(self):
return self.school_name+self.Class+self.page_name
and it's another app name WT
This is its models.py:
from django.db import models
from SFC.models import class_site
# Create your models here.
for i in class_site.objects.all():
code=f'''class {i.school_name + i.Class}subject(models.Model):
subject = models.CharField('科目名',max_length=10)
class {i.school_name + i.Class}WorkType(models.Model):
subject = models.ForeignKey({i.school_name + i.Class}subject , on_delete=models.CASCADE)
work_name = models.CharField('功課名稱',max_length=10,default='習作')
common_name = models.CharField('功課俗名',max_length=10,default='國習')
class {i.school_name + i.Class}ExamType(models.Model):
subject = models.ForeignKey({i.school_name + i.Class}subject , on_delete=models.CASCADE)
work_name = models.CharField('考試名稱',max_length=10,default='大卷')
common_name = models.CharField('功課俗名',max_length=10,default='國卷')
'''
exec(code)
It send me:
class CKJH802WorkType(models.Model):
^
IndentationError: unindent does not match any outer indentation level
I tried not to use f string but i tkink it may be work by using f string.
What's wrong with models.py of WT?
The reason I don't use the foreignkey is because the site is for other people to use, It may cause some mistake.
This is website for education and everyone can ask for create his own class website, it will creates a new class website for him. It can lets the person in charge of the class enter the homework daily.If someone makes the mistake , it would influences the website for other class.
Use textwrap.dedent(text) to handle the indentation from the left side.
Your code:
code=f'''class {i.school_name + i.Class}subject(models.Model):
subject = models.CharField('科目名',max_length=10)
class {i.school_name + i.Class}WorkType(models.Model):
subject = models.ForeignKey({i.school_name + i.Class}subj
is basically this:
class ...:
subject = ...
class ...:
subject = ...
which is causing the indentation error and should look like this:
code=f'''
class {i.school_name + i.Class}subject(models.Model):
subject = models.CharField('科目名',max_length=10)
class {i.school_name + i.Class}WorkType(models.Model):
subject = models.ForeignKey({i.school_name + i.Class}subj
or:
code=f'''\ # to skip the initial empty first line
class {i.school_name + i.Class}subject(models.Model):
subject = models.CharField('科目名',max_length=10)
class {i.school_name + i.Class}WorkType(models.Model):
subject = models.ForeignKey({i.school_name + i.Class}subj
Also, if you really need to solve it this way, try to use metaclasses instead of the combination of exec() + F-strings as it just asks for an exploitation or a nasty bug. Otherwise, just try to use foreign keys or other, saner approach to DB structure + ORM.

Pass array of links in mutation [graphene/python/graphql]

i'm a little bit confuses about using graphene.
I am using the example of mutations on https://www.howtographql.com/graphql-python/3-mutations/ , but here only the example is shown how to create ONE link. Now it is more realistic for me that you have a list of links or other objects that you pass to your backend and later database. Is there anyone who has already implemented such an example?
I have taken a different example from https://docs.graphene-python.org/en/latest/types/mutations/#inputfields-and-inputobjecttypes . Below code snippet should help you in creating multiple instances in a single mutation.
import graphene
from .models import Person
class PersonInput(graphene.InputObjectType):
name = graphene.String(required=True)
age = graphene.Int(required=True)
class PersonType(DjangoObjectType):
class Meta:
model = Person
class CreatePerson(graphene.Mutation):
class Arguments:
person_objects = graphene.List(PersonInput, required=True)
persons = graphene.List(PersonType)
def mutate(root, info, person_objects):
persons = list()
for person_data in person_objects:
person = Person.objects.create(
name=person_data.name,
age=person_data.age
)
persons.append(person)
return CreatePerson(persons=persons)
mutation:
createPerson(personObjects: [{name: "Danish Wani" age:28}, {name: "Wani Danish" age:29}]){
persons{
name
age
}
}

How can I override a DjangoModelFormMutation field type in graphene?

I'm building a simple recipe storage application that uses the Graphene package for GraphQL. I've been able to use Django Forms so far very easily in my mutations, however one of my models fields is really an Enum and I'd like to expose it in Graphene/GraphQL as such.
My enum:
class Unit(Enum):
# Volume
TEASPOON = "teaspoon"
TABLESPOON = "tablespoon"
FLUID_OUNCE = "fl oz"
CUP = "cup"
US_PINT = "us pint"
IMPERIAL_PINT = "imperial pint"
US_QUART = "us quart"
IMPERIAL_QUART = "imperial quart"
US_GALLON = "us gallon"
IMPERIAL_GALLON = "imperial gallon"
MILLILITER = "milliliter"
LITER = "liter"
# Mass and Weight
POUND = "pound"
OUNCE = "ounce"
MILLIGRAM = "milligram"
GRAM = "gram"
KILOGRAM = "kilogram"
My Model:
class RecipeIngredient(TimeStampedModel):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredients')
direction = models.ForeignKey(RecipeDirection, on_delete=models.CASCADE, null=True, related_name='ingredients')
quantity = models.DecimalField(decimal_places=2, max_digits=10)
unit = models.TextField(choices=Unit.as_tuple_list())
My form:
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = (
'recipe',
'direction',
'quantity',
'unit',
)
My Mutation:
class CreateRecipeIngredientMutation(DjangoModelFormMutation):
class Meta:
form_class = RecipeIngredientForm
exclude_fields = ('id',)
I've created this graphene enum UnitEnum = Enum.from_enum(Unit) however I haven't been able to get graphene to pick it up. I've tried adding it to the CreateRecipeIngredientMutation as a regular field like unit = UnitEnum() as well as an Input class on that mutation. So far, the closest I've gotten is this Github issue from awhile ago. After playing around with the class in an iPython shell, I think I could just do CreateRecipeIngredientMutation.Input.unit.type.of_type = UnitEnum() but this feels awful.
I came up with a solution that works but is not pretty. I used the https://github.com/hzdg/django-enumfields package to help with this.
I created my own form field:
class EnumChoiceField(enumfields.forms.EnumChoiceField):
def __init__(self, enum, *, coerce=lambda val: val, empty_value='', **kwargs):
if isinstance(enum, six.string_types):
self.enum = import_string(enum)
else:
self.enum = enum
super().__init__(coerce=coerce, empty_value=empty_value, **kwargs)
And used it in my Django form. Then in my custom AppConfig I did this:
class CoreAppConfig(AppConfig):
name = 'myapp.core'
def ready(self):
registry = get_global_registry()
#convert_form_field.register(EnumChoiceField)
def convert_form_field_to_enum(field: EnumChoiceField):
converted = registry.get_converted_field(field.enum)
if converted is None:
raise ImproperlyConfigured("Enum %r is not registered." % field.enum)
return converted(description=field.help_text, required=field.required)
And finally in my schema:
UnitEnum = Enum.from_enum(Unit)
get_global_registry().register_converted_field(Unit, UnitEnum)
I really don't like this, but couldn't think of a better way to handle this. I came across this idea when searching down another graphene django issue here https://github.com/graphql-python/graphene-django/issues/481#issuecomment-412227036.
I feel like there has to be a better way to do this.

How can I use multiple models to point to the same collection?

I have a single collection that can represent multiple types of data:
class Taxes(db.Document):
meta = {'collection': 'taxes'}
type = db.StringField() # State, local, federal
owner = db.ReferenceField(User, unique=True)
name = db.StringField()
fiscal_year = db.IntField()
What I am wanting to do is have either a DynamicEmbeddedDocument or make this a DynamicDocument to hold different models.
For example:
class Taxes(db.Document):
...
# This is made up syntax
data = db.EmbeddedDocumentField(StateTaxes, LocalTaxes, FederalTaxes)
Or:
class Taxes(db.DynamicDocument):
...
class StateTaxes(Taxes):
state_name = db.StringField()
class LocalTaxes(Taxes):
locality_name = db.StringField()
The goal is to do this:
# Embedded Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax.data for tax in taxes if tax.type == 'state']
state_names = [tax_data.state_name for tax_data in state_taxes]
# Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax for tax in taxes if tax.type == 'state']
state_names = [tax.state_name for tax in state_taxes]
Notes:
I must be able to perform 1 query to get back all types**.
Models should be separate in order to allow for clean definitions.
This example is very small, there would be a growing number of Models with very different definitions**.
All Models will have 4 or 5 fields that are the same.
The dynamic data should be relatively easy to query.
**These are the main reasons I am not using separate collections
Is this possible?
You could make a base class that covers all the base attributes (fields) and methods that you need. For example:
class BaseTaxes(db.Document):
name = db.StringField()
value = db.IntegerField()
meta = {'allow_inheritance': True}
def apply_tax(self, value):
return value*(1+self.value)
With this base class you can then create different versions:
class StateTaxes(BaseTaxes):
state = db.StringField()
As such the StateTaxes class inherits both attributes of BaseTaxes and its methods (more details here). Because it inherits the BaseTaxes class, it will be saved in the same collection (BaseTaxes) and queries can reach all subclasses:
results = BaseTaxes.objects().all()
And then, to split results by subclass:
state_taxes = [item for item in results if isinstance(item,StateTaxes)]

Search by a property of a reference

I have the following models:
class Station(db.Model):
code = db.StringProperty(required=True)
name = db.StringProperty(required=True)
class Schedule(db.Model):
tripCode = db.StringProperty(required=True)
station = db.ReferenceProperty(Station, required=True)
arrivalTime = db.TimeProperty(required=True)
departureTime = db.TimeProperty(required=True)
How can I order programatically all the Schedules by Station's name?
Something like Schedule.all().order('station.name')
You will to de-normalize your models or sort the results in memory:
Schedule.all().fetch(100).sort(key=lambda s: s.station.name)
(code not tested)
After use sort i think you need to fetch all entities:
Schedule.all().fetch (100).sort(key=lambda s: s.station.name)
May be you can also use collection name.
But i think the jbochi answer is better :)
[x.schedule_set.get () for x in Station.all ().order ('name')]

Categories

Resources