Error Unit testing a ModelForm - python

I am currently writing unit tests for my Django webapp and have come across a stumbling block.
My Model:
class Employment(models.Model):
applicant = models.ForeignKey(Applicant)
company = models.CharField(max_length=50)
town = models.CharField(max_length=50)
salary = models.CharField(max_length=50)
job_title = models.CharField(max_length=50)
responsibilities = models.CharField(max_length=100)
date_from = models.DateField('Date From')
date_to = models.DateField('Date To')
reason_for_leaving = models.CharField(max_length=50)
My Form:
class EmploymentForm(forms.ModelForm):
error_css_class = 'error' #set some css when an error
company = forms.CharField(label="Company/Agency")
town = forms.CharField(label="Town/City")
salary = forms.CharField(label="Salary/Pay rate")
date_from = forms.DateField(('%d/%m/%Y',), widget=forms.DateInput(format='%d/%m/%Y', attrs={'placeholder':'dd/mm/yyyy'}))
date_to = forms.DateField(('%d/%m/%Y',), widget=forms.DateInput(format='%d/%m/%Y', attrs={'placeholder':'dd/mm/yyyy'}))
class Meta:
model = Employment
My Test:
"""
Complete employment form passes validation
"""
def test_employment_form_complete(self):
applicant = Applicant(job_id=1)
data = {
'company': 'test company',
'town': 'test town',
'salary': '1000000',
'job_title': 'test job name',
'responsibilities': 'test responsibilities',
'date_from': '01/01/1990',
'date_to': '01/02/1991',
'reason_for_leaving': 'test reason for leaving'
}
employment_form = EmploymentForm(instance=applicant, data=data)
result = employment_form.is_valid()
print "errors %s" % employment_form.errors
self.assertEqual(result, True)
If I run the test like this I get:
<ul class="errorlist"><li>applicant<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
If I add:
'applicant': applicant
to my data object it complains it need to be an int.
If I add:
'applicant': 1
(or another integer)
it returns:
<ul class="errorlist"><li>applicant<ul class="errorlist"><li>Select a valid choice. That choice is not one of the available choices.</li></ul></li></ul>
which is understandable really.
How can I get around this? What is the best practise?
Regards,
Chris.

I think You should call applicant.save()
You create new object, but did't save it to database. while testing django create "temporary" database.
Also add to data dictionary 'applicant': 1.
PS. Make sure applicant object will be created with foreign key=1!!
Completely working code.
def test_employment_form_complete(self):
"""
Complete employment form passes validation
"""
applicant = Applicant(job_id=1)
applicant.save()
data = {
'applicant': 1,
'company': 'test company',
'town': 'test town',
'salary': '1000000',
'job_title': 'test job name',
'responsibilities': 'test responsibilities',
'date_from': '01/01/1990',
'date_to': '01/02/1991',
'reason_for_leaving': 'test reason for leaving'
}
employment_form = EmploymentForm(instance=applicant, data=data)
result = employment_form.is_valid()
print "errors %s" % employment_form.errors
self.assertEqual(result, True)

Related

How Do I transfer data from sale order to invoice when clicking on 'Create Invoice' in Odoo 14 or Flectra 1.7?

I run Flectra inside a Docker container. I have custom fields in sale.order which I want to transfer to account.invoice.
class SaleOrder(models.Model):
_inherit = 'sale.order'
myField = fields.Integer(string='My Field', default=21, required = True)
#api.multi
def _prepare_invoice(self):
res = super(SaleOrder, self)._prepare_invoice()
# res.update({
# 'myField': self.myField,
# })
res['myField'] = self.myField
return res
class SaleInvoice(models.Model):
_inherit = 'account.invoice'
myField = fields.Integer(string='My Field', default=21, required = True)
I tried to override _prepare_invoice and also _create_invoices in different variations, but none worked. From my understanding they should have worked, but I am new to Odoo/Flectra, so I would be happy for any help.
I use Flectra 1.7 (Community Edition) which I think corresponds to Odoo 14.
Try this:
def create_invoice(self):
for rec in self:
invoice = rec.env['account.move'].create({
'move_type': 'out_invoice',
# 'partner_id': self.partner.id,
'journal_id': 18, # say u forget to create journal
# 'currency_id': self.env.ref('base.USD').id,
'payment_reference': 'invoice to client',
'invoice_line_ids': [(0, 0, {
'product_id': self.env['product.product'].create({'name': 'A Test Product'}),
'quantity': 1,
'price_unit': 40,
'name': 'Your project product',
})],
})

Python SQLAlchemy TypeError: unhashable type: 'dict' when trying to instantiate model with one-to-many relationship

I'm trying to use SQLAlchemy to create a model which contains a one-to-many relationship. One recipe may have many directions associated with it. However, when I try to instantiate a recipe I get TypeError: unhashable type: 'dict'. If I remove the directions argument everything works fine and it creates the recipe without any directions. Is there something I'm missing that won't allow the directions parameter to be a list?
app.py
data = {
'cook_time': 15,
'description': 'Recipe description',
'directions': [{'order': 1, 'text': 'First direction'},
{'order': 2, 'text': 'Second direction'}],
'image_url': 'https://via.placeholder.com/800x300?text=Recipe+Image',
'name': 'Test recipe 2',
'prep_time': 15,
'servings': 6
}
recipe = models.Recipe(
name=data['name'],
description=data['description'],
image_url=data['image_url'],
prep_time=data['prep_time'],
cook_time=data['cook_time'],
servings=data['servings'],
directions=data['directions']
)
models.py
class Recipe(db.Model):
__tablename__ = 'recipes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), index=True)
description = db.Column(db.String(2000))
image_url = db.Column(db.String(200))
prep_time = db.Column(db.Integer)
cook_time = db.Column(db.Integer)
servings = db.Column(db.Integer)
directions = db.relationship('RecipeDirection', backref='recipes', lazy='dynamic')
class RecipeDirection(db.Model):
__tablename__ = 'recipe_directions'
id = db.Column(db.Integer, primary_key=True)
recipe_id = db.Column(db.Integer, db.ForeignKey('recipes.id'))
order = db.Column(db.Integer)
text = db.Column(db.String(1000))
You are getting the error because SQLAlchemy is expecting directions to be a list of RecipeDirection. To fix, create a list of RecipeDirection first.
data = {
'cook_time': 15,
'description': 'Recipe description',
'directions': [{'order': 1, 'text': 'First direction'},
{'order': 2, 'text': 'Second direction'}],
'image_url': 'https://via.placeholder.com/800x300?text=Recipe+Image',
'name': 'Test recipe 2',
'prep_time': 15,
'servings': 6
}
# Create a list of `RecipeDirection`
directions = []
for direction in data.get("directions", []):
directions.append(models.RecipeDirection(**direction))
recipe = models.Recipe(
name=data['name'],
description=data['description'],
image_url=data['image_url'],
prep_time=data['prep_time'],
cook_time=data['cook_time'],
servings=data['servings'],
directions=directions # Now list of RecipieDirection not list of dicts
)
I would also suggest looking into a serilizer that will take care of some of the details of marshalling and serilizing nested data structures for you, such as marshmallow-sqlalchemy

split comma separated JSON

I am trying to implement a search functionaltiy for a restaurants, currently in my database the the field menu_type is a varchar, the values are separated by commas
"Burger, Cafe, Italian, American,Irish"
how can I make it so that when somebody searches for "Burger" it will show him all the results which have Burger only as menu_type OR "Burger" is a part of their menu_type
#app.route('/api/restaurants/search/',methods=['GET'])
def get_restaurant():
menu = request.json.get('menu')
restaurant = Restaurant.query.filter_by(menu_type=menu).all()
return jsonify(json_list=[i.serialize for i in restaurant])
my restaurant model:
class Restaurant(db.Model):
__tablename__ ='Restaurant'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
address1 =db.Column(db.String(128))
address2 = db.Column(db.String(32))
phone =db.Column(db.Integer)
lat = db.Column(db.Float(precision='12,10'))
lng = db.Column(db.Float(precision='12,10'))
cost = db.Column(db.Integer)
menu_type = db.Column(db.String(64))
rate =db.Column(db.Float(precision='3,2'))
offer=db.Column(db.String(128))
#property
def serialize(self):
"""Return object data in easily serializeable format"""
return {
'id' : self.id,
'name': self.name,
'address1' : self.address1,
'address2': self.address2,
'phone' : self.phone,
'lat': self.lat,
'lng' : self.lng,
'cost': self.cost,
'menu_type' : self.menu_type,
'rate': self.rate,
'offer' : self.offer
}
Use the following for filtering:
Restaurant = Restaurant.query.filter(Restaurant.menu_type.like('% {0}%').format(menu)).all()
This will give you the result because you are doing a like query.

Odoo error when generating One2many field data

Given the following class for the header:
class vontatas_head(models.Model):
_name = 'vontatas.head'
display_name = fields.Char(string="Sor", compute='_compute_display_name', store=False)
plan_type_id = fields.Many2one(
comodel_name='plan.type', string='Terv típus', required=True)
year = fields.Integer(string='Év', required=True, default=lambda *a: strftime('%Y'))
version = fields.Integer(string='Verzió', required=True, default=1)
comment = fields.Char(string='Megjegyzés')
vontatas_data_ids = fields.One2many(
comodel_name='vontatas.data', inverse_name='vontatas_id', string='Adatok', default=get_default_lines)
And for the detail:
class vontatas_data(models.Model):
_name = 'vontatas.data'
vontatas_id = fields.Many2one(comodel_name="vontatas.head", string="Vontatás sor")
name = fields.Char(string="Megnevezés", required=True)
code = fields.Char(string="Kód", required=True)
type = fields.Selection([('total', 'Összesen'), ('input', 'Input')], string="Típus", default='input')
value = fields.Float(string="Várható költség")
parent_id = fields.Many2one(comodel_name="vontatas.data", ondelete='cascade', string="Összesen sor")
child_ids = fields.One2many(comodel_name="vontatas.data", inverse_name='parent_id', string='Input sorok')
I have to automatically generate details data from a template with this code:
def get_default_lines(self):
self.env.cr.execute("select name, code, type, parent_id from vontatas order by code")
sorok = self.env.cr.fetchall()
ids = []
for sor in sorok:
ids.append((0,0, { 'name': sor[0],
'code': sor[1],
'type': sor[2],
'parent_id': sor[3]
}))
return ids
Everyting is working fine, but at the creation I have an error message: "One of the documents you are trying to access has been deleted, please try again after refreshing."
I know why this error is happening: at the moment of generation there is no valid parent_id.
My question is: how to provide any valid parent_id within the function get_default_lines, knowing that the data is actually just in memory, not stored yet in the database?
Or asked otherwise: How to keep the hierarchy level defined within the template?

Django How to iterate over list returned from ldap to save new object

I have the following model:
class SystemUsers(models.Model):
username = models.CharField(max_length=25)
displayName = models.CharField(max_length=100)
phoneNumber = models.BigIntegerField(max_length=10)
emailAddress = models.EmailField(max_length=100)
employeeNumber = models.CharField(max_length=7)
firstName = models.CharField(max_length=20)
lastName = models.CharField(max_length=30)
I have an ldap query that is returning the following from active directory:
user_details = []
for entry in results:
user_details.append(entry[1]['sAMAccountName'][0].lower())
user_details.append(entry[1]['displayName'][0].replace(",", " "))
user_details.append(entry[1]['telephoneNumber'][0].replace("-", ""))
user_details.append(entry[1]['mail'][0].lower())
user_details.append(entry[1]['employeeID'][0].lower())
user_details.append(entry[1]['givenName'][0])
user_details.append(entry[1]['sn'][0])
return user_details
I am getting results as expected but I can't parse out the list to put it into a SystemUser(....).save() block to save it to the database. All I get is a list I can't loop over or set variables for.
When I do a
for item in user_details:
print(item)
All I have are 7 lines of values. I can't get it so that item[0] is username, item[1] is displayname, etc, etc
You can create the dict with the user data and use the kwargs magic:
field_names = ('username', 'displayName', 'phoneNumber', 'emailAddress',
'employeeNumber', 'firstName', 'lastName', )
data = dict(zip(field_names, user_details)
SystemUser.objects.create(**data)
BTW may be it is a better idea to get the user_details as a dictionary from the beginning?
user_details = {
'username': entry[1]['sAMAccountName'][0].lower(),
'displayName': entry[1]['displayName'][0].replace(",", " "),
'phoneNumber': entry[1]['telephoneNumber'][0].replace("-", ""),
'emailAddress': entry[1]['mail'][0].lower(),
'employeeNumber': entry[1]['employeeID'][0].lower(),
'firstName': entry[1]['givenName'][0],
'lastName': entry[1]['sn'][0],
}
SystemUser.objects.create(**user_details)

Categories

Resources