I'm working with Flask/Mongoengine-MongoDB for my latest web application.
I'm familiar with Pymongo, but I'm new to object-document mappers like Mongoengine.
I have a database and collection set up already, and I basically just want to query it and return the corresponding object. Here's a look at my models.py...
from app import db
# ----------------------------------------
# Taking steps towards a working backend.
# ----------------------------------------
class Property(db.Document):
# Document variables.
total_annual_rates = db.IntField()
land_value = db.IntField()
land_area = db.IntField()
assessment_number = db.StringField(max_length=255, required=True)
address = db.StringField(max_length=255, required=True)
current_capital_value = db.IntField
valuation_as_at_date = db.StringField(max_length=255, required=True)
legal_description = db.StringField(max_length=255, required=True)
capital_value = db.IntField()
annual_value = db.StringField(max_length=255, required=True)
certificate_of_title_number = db.StringField(max_length=255, required=True)
def __repr__(self):
return address
def get_property_from_db(self, query_string):
if not query_string:
raise ValueError()
# Ultra-simple search for the moment.
properties_found = Property.objects(address=query_string)
return properties_found[0]
The error I get is as follows: IndexError: no such item for Cursor instance
This makes complete sense, since the object isn't pointing at any collection. Despite trolling through the docs for a while, I still have no idea how to do this.
Do any of you know how I could appropriately link up my Property class to my already extant database and collection?
The way to link a class to an existing collection can be accomplished as such, using meta:
class Person(db.DynamicDocument):
# Meta variables.
meta = {
'collection': 'properties'
}
# Document variables.
name = db.StringField()
age = db.IntField()
Then, when using the class object, one can actually make use of this functionality as might be expected with MongoEngine:
desired_documents = Person.objects(name="John Smith")
john = desired_documents[0]
Or something similar :) Hope this helps!
I was googling this same question and i noticed the answer has changed since the previous answer:
According to the latest Mongoengine guide:
If you need to change the name of the collection (e.g. to use MongoEngine with an existing
database), then create a class dictionary attribute called meta on your document, and set collection to the
name of the collection that you want your document class to use:
class Page(Document):
meta = {'collection': 'cmsPage'}
The code on the grey did the trick and i could use my data instantly.
Related
I have a model called Video, and it has related objects on another model called Label. Example here:
class Video(models.Model):
pass
class Label(models.Model):
video = models.ForeignKey(Video, related_name="labels")
current = models.NullBooleanField()
I need to be able to find the current label on a video by doing something like my_video.labels.filter(current=True), and this query should only ever return one label, so only one label on the video should have that field set to True.
Is there a way of ensuring this on the model/db?
Thanks
EDIT: The answer given below has achieved exactly this. Adding some django tests below for anyone else reading as some proof:
class TestLabelIntegrity(TestCase):
def test_a_video_can_have_only_one_current_label(self):
video = Video.objects.create()
label_1 = Label.objects.create(
video=video,
current=True
)
with self.assertRaises(IntegrityError):
label_2 = Label.objects.create(
video=video,
current=True
)
def test_two_different_videos_can_each_have_current_layers(self):
""" No assertions needed, just need to make sure no integrity errors are raised"""
video_1 = Video.objects.create()
label_1 = Label.objects.create(
video=video_1,
current=True
)
video_2 = Video.objects.create()
label_2 = Label.objects.create(
video=video_2,
current=True
)
I believe you can solve this using UniqueConstraint.
Using this, you can restrict that a Video only have a single label that current == True
You can define the UniqueConstraint in the models Meta.
You’ll get a database integrity error on save() if the condition fails.
See the documentation for this here:
https://docs.djangoproject.com/en/4.0/ref/models/constraints/
class Label(models.Model):
...
class Meta:
constraints = [
models.UniqueConstraint(
fields=["current", "video"],
condition=Q(current=True),
name="unique_current_label",
),
]
I have an issue regarding factory boy using in the testing of my Lets assume I have this three models:
Class Company(models.Model):
name = str
Class Domain(models.Model):
company = ForeignKey(ref=Company)
name = str
created_at = datetime
Class Record(models.Model):
domain = ForeignKey(ref=Domain)
name = str
created_at = datetime
CompanyFactory(factory.django.DjangoModelFactory):
name = str
DomainFactory(factory.django.DjangoModelFactory):
company = factory.SubFactory(CompanyFactory)
name = str
created_at = datetime
RecordFactory(factory.django.DjangoModelFactory):
domain = factory.SubFactory(DomainFactory)
name = str
created_at = datetime
Having this, when I'm testing the Record views, at the begginning of every view I check that the Domain object is, in fact, related to the Company object such as:
try:
domain = Domain.objects.get(domain=domain_id, company__id=company_id)
except ObjectDoesNotExist:
return Response(
data={"message": "Domain isn't related to the company provided."}, status=status.HTTP_403_FORBIDDEN
)
But this code always returns an ObjectDoesNotExist exception when I make the testing with pytest+factory-boy but when I do manual testing runs fine. Have you experienced something similar? What I'm missing here?
Thanks in advance.
As requested per #gbrennon I'm adding the test code:
Hi! Thanks for answering.
My test code is as it follows:
class RecordCompanyAdminTests(CompanyAdminUser):
def setUp(self):
super(RecordCompanyAdminTests, self).setUp()
self.domain = DomainFactory.create()
self.record = RecordFactory.create()
def test_record_list_get(self):
url = reverse("autoprovisioning:record_list", kwargs={"company_id": self.company.id, "domain_id": self.domain.id})
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
how are u doing?
the test code wasn't included but i'll try to infer things
when I do manual testing runs fine
it seems like u already populated ur database! but listen, when u run ur test suite django will be using a "test database" in favor of isolation!
what u need to do is to create, using the factory_boy lib, ur "data fixtures"!
my suggestion without any context:
class MyTestCase(TestCase):
def setUp(self):
self.existing_domain_in_database = DomainFactory.create(...) # insert here the data to populate this model
and the desired data should already exists in ur "test database" for every test that is going to be run inside of this class
I am building help desk system on django. Where anyone can open ticket for customer support.
Assume I have an parent object #001 and every child object of this parent have same ticket id. See the screenshot for better understand:
child1 and child2 have same ticket id like their parent object. How to apply bulk update on all objects if they have same ticket id?. Assume if I change ticket status of child2 then I want it will also apply bulk update of child1 and parent object. any idea how to do that on django?
here is my code:
models.py
class Contact(models.Model):
choice = (("pending","pending"),("solved","solved"),("closed","closed"))
ticket_status = models.CharField(choices=choice,max_length=100,default="pending")
parent =models.ForeignKey('self', on_delete=models.CASCADE,
null=True, blank=True, related_name='contact_parent')
sno = models.AutoField(primary_key=True,)
def save(self,*args,**kwargs):
if not self.parent and not self.support_ticket:
self.support_ticket= str(rand_support_ticket())
if not self.support_ticket:
self.support_ticket = self.parent.support_ticket
super(Contact,self).save(*args,**kwargs)
forms.py
class SupportAgentFrom(forms.ModelForm):
class Meta:
model = Contact
fields = ['support_message','ticket_status']
views.py
def AddReplySupport(request,slug):
# fetch the object related to passed id
obj = get_object_or_404(Contact, slug = slug)
# pass the object as instance in form
form = SupportAgentFrom(request.POST or None, instance = obj)
if form.is_valid():
form.instance.support_agent = request.user
form.save()
now I can update only single object once at a time. I want to apply bulk update on multiple objects at a time if they have same ticket id.
#Update1
Finally my problem is solved after following Dan Yishai solution. Here I want to try little bit explain his code so people can understand and solve this type of similar problems which I was facing.
Contact.objects.filter(
Q(support_ticket=form.instance.support_ticket)
).update( ticket_status="closed")
Above line of code searching and updating only those objects whose have exactly same ticket id.
You can update the item and all of it's children in a single query, just replace your code inside the if with something like:
count = Contact.objects.filter(
Q(pk=form.instance.pk) | Q(parent_id=form.instance.pk)
).update(support_agent=request.user)
You can use count to verify at least 1 object has been updated, and display to the user how many objects were modified.
Below I'm assuming sno is the Ticket Id
To grab the queryset:
queryset = Contact.objects.filter(sno=form.instance.sno)
Now you can use .update() or .bulk_update().
Update every object to have the same support agent:
queryset.update(support_agent=request.user)
Update every object to have a different support agent:
for contact in queryset:
contact.support_agent = value
queryset.bulk_update['support_agent']
This is my structure:
class Imprint_Location(models.Model):
_name = 'imprint.location'
name = fields.Char()
product_id = fields.Many2one('product.template')
class Imprint_Charges(models.Model):
_name = 'imprint.charge'
_rec_name = 'location_id'
product_id_c = fields.Many2one('product.template', required=True)
location_id = fields.Many2one('imprint.location', required=True)
#api.multi
#api.onchange('product_id_c', 'location_id')
def product_filter(self):
res = {}
print '\n\n-------\n\n', self, self.product_id_c, '\n\n-------\n\n'
if self.product_id_c:
res['domain'] = {'location_id': [('product_id', '=', self.product_id_c.id)]}
print res
return res
class Product_Template(models.Model):
_inherit = 'product.template'
imprint_location_ids = fields.One2many('imprint.location', 'product_id')
sale_imprint_charge_ids = fields.One2many('imprint.charge', 'product_id_c')
Now i have defined a page in product.template and inside the page is sale_imprint_charge_ids which is in <tree editable="bottom"> and i am not selecting the product_id_c field[also this field doesn't show up in the tree defined].
Now my problem here is that when i select this from the form view which i defined for imprint.charge the method product_filter works fine, but when i enter from the product.template then i get a error saying
TypeError: <odoo.models.NewId object at 0x7fbb8bc21b90> is not JSON serializable
Because from product.template if passes the object <odoo.models.NewId object at 0x7fbb8bc21b90> , so if print self.product_id_c then it prints product.template(<odoo.models.NewId object at 0x7fbb8bc21b90>) so this is not serializable. i have tried doing self.product_id_c.ids which give output empty list [].
So how do get the product.template id from the object or pass the id itself overriding some method.
You should improve couple of following points.
res['domain'] = {'location_id': [('product_id', '=', self.product_id_c.id)]}
return res
study some search() method of ORM
Try with following code:
#api.multi
#api.onchange('product_id_c', 'location_id')
def product_filter(self):
res = {}
if self.product_id_c:
self.location_id = False
#search product_template in imprint.locationwith table and limit we will get only record if related record found
location_id = self.env['imprint.location'].search([('product_id', '=', self.product_id_c.id)], limit=1)
if location_id:
#location_id.ids will give you something like [2] so we need to set value as 2
self.location_id = location_id.ids[0]
EDIT:
As per your first comment, you need a list of related location then we should following trick.
Remove product_filter() method
Add domain in imprint.charge object view file
For example:
<field name="location_id" domain="[('product_id', '=', product_id_c)]"/>
Afterwards, Restart Odoo server and upgrade your custom module.
When creating a brand new record Odoo creates that wierd <odoo.models.NewId object at 0x7fbb8bc21b90> object. After you have written the record this id gets turned into the normal ids that you are used to (an integer). In this situation you have a function which (not unreasonably) expects a real id value at a point when no such value really exists. You need to provide a fallback, such as evaluating if the id is an integer and providing an alternate value in that circumstance. Although your function seems to return an object which I dont quite know what you are expecting to happen. If you wish to modify the value of one of your fields I would modify the values of the self object rather that returning an object.
I have a snippet for countrycodes and I want to define localized country names on the root pages for each localized site.
The snippet looks like this:
#register_snippet
class Country(models.Model):
iso_code = models.CharField(max_length=2, unique=True)
panels = [
FieldPanel('iso_code'),
]
def get_iso_codes():
try:
countries = Country.objects.all()
result = []
for country in countries:
result.append((country.iso_code,country.iso_code))
return result
except Country.DoesNotExist:
return []
Now I want to call the function get_iso_codes when creating a choiceblock and fill the choices from the snippet.
The block looks like this
class CountryLocalizedBlock(blocks.StructBlock):
iso_code = blocks.ChoiceBlock(choices=Country.get_iso_codes(), unique=True)
localized_name = blocks.CharBlock(required=True)
However, when calling manage.py makemigrations I get the following error:
psycopg2.ProgrammingError: relation "home_country" does not exist
LINE 1: ..."."iso_code", "home_country"."sample_number" FROM "home_coun...
I can bypass this by commenting out 'Country.objects.all()' and then running makemigrations and later readding the line again to the code, however I would prefer a solution that does not require this workaround (also it fails when I run 'manage.py collectstatic' when building before deployment and I don't know how to work around this and am stuck)
I found a solution based on Wagtail, how do I populate the choices in a ChoiceBlock from a different model?
The country class remains untouched (except that the get_iso_codes method is now superflous). I've just extended Chooserblock and use Country as my target_model:
class CountryChooserBlock(blocks.ChooserBlock):
target_model = Country
widget = forms.Select
def value_for_form(self, value):
if isinstance(value, self.target_model):
return value.pk
else:
return value
And used the CountryChooserBlock instead of the ChoiceBlock:
class CountryLocalizedBlock(blocks.StructBlock):
iso_code = CountryChooserBlock(unique=True)
localized_name = blocks.CharBlock(required=True)