Create a canonical "parent" product in Django Oscar programmatically - python

I'm trying to use a modified version of the django-oscar import_oscar_catalogue class to import a bunch of products from a CSV, and on the first encounter of a product (defined by title), create a canonical parent product, and then for all future encounters create a child product under that parent product.
This seems to work, but the canonical product does not reflect the combined stock levels of the child product, nor display the correct attributes for that product. It does correctly list them as variations within the django dashboard though.
How can I programmatically create this child/parent relationship in products, with the correct stock records?
Relevant code:
def _create_item(self, upc, title, product_class, other_product_attributes):
product_class, __ \
= ProductClass.objects.get_or_create(name=product_class)
try:
parent = Product.objects.get(title=title)
item = Product()
item.parent = parent
except Product.DoesNotExist:
# Here is where I think it might need to be changed
# Maybe pitem = ParentProduct() or something?
pitem = Product()
pitem.upc = upc
pitem.title = title
pitem.other_product_attributes = other_product_attributes
# Here parent item is saved to db
pitem.save()
# Create item because no parent was found
item = Product()
parent = Product.objects.get(title=title)
#Set parent
item.parent = parent
# Customize child attributes
item.product_class = product_class
item.title = title
item.other_product_attributes = other_product_attributes
# Save the child item
item.save()
def _create_stockrecord(self, item, partner_name, partner_sku, price_excl_tax,
num_in_stock, stats):
# Create partner and stock record
partner, _ = Partner.objects.get_or_create(
name=partner_name)
try:
stock = StockRecord.objects.get(partner_sku=partner_sku)
except StockRecord.DoesNotExist:
stock = StockRecord()
stock.num_in_stock = 0
# General attributes
stock.product = item
stock.partner = partner
# SKU will be unique for every object
stock.partner_sku = partner_sku
stock.price_excl_tax = D(price_excl_tax)
stock.num_in_stock += int(num_in_stock)
# Save the object to database
stock.save()
The create_stockrecord() creates a record of 1 stock for each unique item variation, but these variation's stockrecords don't translate to the parent item.
EDIT:
I have updated the class with a method that explicitly calls ProductClass.objects.track_stock() against the ProductClass instance, and I'm calling it after looping through all rows of the CSV file (passing it the name of the one product class I use currently). However, when looking at the stock in dashboard, none of the child/variations stock is being counted against the parent.
def track_stock(self, class_name):
self.logger.info("ProductClass name: %s" % class_name)
product_class = ProductClass.objects.get_or_create(name=class_name)
self.logger.info("ProductClass: %s" % str(product_class))
self.logger.info("TrackStock: %s" % str(product_class[0].track_stock))
product_class[0].track_stock = True
self.logger.info("TrackStock: %s" % str(product_class[0].track_stock))
product_class[0].save()
INFO Starting catalogue import
INFO - Importing records from 'sample_inventory.csv'
INFO - Flushing product data before import
INFO Parent items: 6, child items: 10
INFO ProductClass name: ClassName
INFO ProductClass: (<ProductClass: ClassName>, False)
INFO TrackStock: True
INFO TrackStock: True
I have checked the admin page, only 1 ProductClass is created, and it has the same name as is being passed to track_stock(). Is there something else that needs to be done to enable this feature? track_stock() documentation is kind of sparse. In the output, track_stock looks like it is true in both instances. Does it have to be False while the child_objects are created, and then flipped to True?

To be able to correctly reflect the stock levels for any Product you need to have a Partner that will supply the Product and then you need to have StockRecord that links the Partner and the Products together.
First make sure that you have all that information in the database for each one of your Product variations.
Then you need to update your ProductClass and set the "track_stock" attribute as True since its None by default.
You also need to remove the ProductClass from your child products since they inherit the ProductClass from their Parent Product.
EDIT 1:
To add attributes to a Product you have to add a ProductAttribute for the ProductClass and then you can set the attributes directly on the Product like this example.
EDIT 2:
You also need to set the "net_stock_level" on the StockRecord.
To get a more in depth look into how Oscar gets the stock levels look into Selector. This class determines which pricing, tax and stock level strategies to use which you might need to customize in the future if you want to charge tax or offer different pricing based on the user.

After some research from the test factory, I solved the issue by specifying
product.stucture = 'parent'
On the parent object, and
product.structure = 'child'
on the child object. I also needed to change the custom attributes of my objects to a dict product_attributes, and then set each value on the object:
if product_attributes:
for code, value in product_attributes.items():
product_class.attributes.get_or_create(name=code, code=code)
setattr(product.attr, code, value)
It was not necessary to create a stock record for each parent object, as they track the stock records of the child objects to which they are associated. It was also not necessary to set track_stock = True, as it is set to True by default when creating a Product()
This answer was posted as an edit to the question Create a canonical "parent" product in Django Oscar programmatically by the OP Tui Popenoe under CC BY-SA 3.0.

Related

Conflict between dataset and catalog

I am trying to add some tags inside the dataset definition on my XML. According to the European Data Portal de structure I must follow for the tag
<foaf:Agent rdf:about="URI/of/the/publisher">
<foaf:name xml:lang="es"> Name of the publisher</foaf:name>
</foaf:Agent>
<dct:publisher rdf:resource=”URI/of/the/publisher”>
Right now inside my dcat:Dataset I can only add the dct:publisher, missing the two other tags.
In my code I have asigned the name as a literal and for the URI an URIref since it must be values for an RDF.
g = self.g
g.add((dataset_ref, RDF.type, DCAT.Dataset))
publishers = dataset_dict.get(dhc.EXPORT_AVAILABLE_PUBLISHERS, {})
organization_id = dataset_dict.get('publisher')
if organization_id in publishers:
publisher = publishers.get(organization_id)
else:
publisher = []
org = h.get_organization(organization_id, False)
publisher = [org.get('title'), None, None]
if org and org['extras']:
for extra in org.get('extras'):
if extra and 'key' in extra and extra['key'] == dhc.ORG_ID_:
notation = extra.get('value')
publisher[1] = dhc.PUBLISHER_PREFIX + notation
publisher[2] = notation
publishers[organization_id] = publisher
dataset_dict[dhc.EXPORT_AVAILABLE_PUBLISHERS] = publishers
if publisher:
# self._add_resource_list_triple(dataset_ref, DCT.publisher, publisher[1], publisher[0], None, None, publisher[2])
EDP_publisher = URIRef(publisher[1])
g.add((dataset_ref, DCT.publisher, EDP_publisher))
g.add((dataset_ref, FOAF.Agent, EDP_publisher))
g.add((EDP_publisher, FOAF.name, Literal(publisher[0])))
So everytime I use the g.add with FOAF.name and FOAF.Agent, it sends my tag out of the dataset tag (dcat:Dataset) and transforms it from about to a resource definition.
I am feeling it could be a conflict of code. Where could I be making the wrong defition?
Updated:
After giving it more tries, I've found that the issued for the structure to not get into the "main tag" is a conflict because of the use of "RDF.type" definition since the main structure is g.add((dataset_ref, RDF.type, DCAT.Dataset)) and when I try to create it a parent and child tag, the parent get duplicate with the main tag since it gets the same values, since I found in the documentation that if the child structure doesn't find on the parent a "RDF.type", it will generate it automatically by calling it "rdf:Description"
I only managed to get the followed structured even though, it's not what I need to get.
<dcat:Dataset rdf:about="URI-ref">
<dct:description xml:lang="es"> Description of the dataset </dct:description>
<dct:title xml:lang="en">Title of dataset</dct:title>
<dct:publisher rdf:resource="URI-ref"/>
</dcat:Dataset>
<foaf:Agent rdf:about="URI-ref">
<foaf:name>Name of the publisher</foaf:name>
</foaf:Agent>
I can't manage to add the Foaf:Agent and foaf:name inside the dcat:Dataset.

Django: How to query for child, granchildren, etc. records in a non-horrible way

I am trying to return a list/filter of users in my Employees table that have a nested relationship to the user. For example, I have employees tied to their manager, and I want to be able to query for all the employees under that manager (this includes any employees under any other managers that are under the main manager). So, if user Bob has 2 direct reports, Sally and Brian. And Brian has 2 direct reports, and Sally has 3 direct reports. I want Bob to be able to see all 7 employees. Right now, the only way I could get it to work was through a horrible sequence, as displayed below..I'm hoping their is an easier/more efficient way.
manager = Employees.objects.filter(manager_id=request.user.id).values('manager')
employee_ids = list(Employees.objects.filter(manager=manager.first()['manager']).values_list('employee', flat=True))
employees = [User.objects.get(id=i).username for i in employee_ids]
grandchildren = []
for i in employees:
user_id = User.objects.get(username=i).id
child = list(Employees.objects.filter(manager=user_id).values_list('employee', flat=True))
grandchildren.append(child)
children = list(chain.from_iterable(grandchildren))
for i in children:
user_id = User.objects.get(id=i).id
child = list(Employees.objects.filter(manager=user_id).values_list('employee', flat=True))
grandchildren.append(child)
grandchildren = list(chain.from_iterable(grandchildren))
for i in grandchildren:
employees.append(User.objects.get(id=i).username)
employees = list(set(employees))
Sorry, but your code looks really horrible. First of all, I mean too many DB queries (most of them are very non-optimized or not even needed).
According to your description, I suggest to try something like this:
manager_id = request.user.id
children_ids = list(
Employees.objects.filter(manager_id=manager_id).values_list('employee', flat=True)
)
grandchildren_ids = list(
Employees.objects.filter(manager_id__in=children_ids).values_list('employee', flat=True)
)
# If you want to go deeper, do this in a loop and stop once an empty list of IDs is fetched
# (which means that there are no descendants anymore)
# Combine all IDs and finally fetch the actual users
# (do it only once, and fetch all the users in a single query, not one by one)
employees_ids = children_ids + grandchildren_ids
employees = User.objects.filter(id__in=employees_ids)
P.S.: is this a joke user_id = User.objects.get(id=i).id? :)

How to assign items for loop inside a Model object with Django?

Is it possible to override values inside a Model? I am getting 'MyModel' object does not support item assignment.
-- model.py
class Slider(models.Model):
name = models.CharField(max_length=255)
rows = models.SmallIntegerField(max_length=2)
cat = models.ForeignKey(Products)
order = models.SmallIntegerField()
status = models.BooleanField(default=0)
--- views.py
rows = Slider.objects.all().order_by('order')
for item in rows:
product = Products.objects.filter(cat=item.cat)[:10]
item['product'] = product
print item
Exception Value:'Slider' object does not support item assignment
If you want to persist the value of product in the database for a particular Slider, then add a ForeignKey field to Slider:
class Slider(models.Model):
...
product = models.ForeignKey(Products)
If you want to have stored temporarily, just add a regular attribute to Slider to hold it.
class Slider(models.Model):
...
product = None
In either case, you'd set it like this:
rows = Slider.objects.all().order_by('order')
for item in rows:
item.product = ...
If you prefer you can just leave out the attribute declaration entirely, but this will mean if you go to check for item.product, without having first set it, you would get an AttributeError.

Does a StructuredProperty reference the parent or child?

Does a StructuredProperty reference the parent or child?
class Invoices(ndb.Model): #Child
class Customers(ndb.Model): #Parent
invoice = ndb.StructuredProperty(Invoices)
or...
class Customers(ndb.Model): #Parent
class Invoices(ndb.Model): #Child
customer = ndb.StructuredProperty(Customers)
To answer your question in the context of "what is the better practice for a NoSQL Datastore",
here's what I can offer.
First, you probably want to name your models in the singular, as they should describe a single
Invoice or Customer entity, not several.
Next, using a StructuredProperty implies that you'd like to keep all of this information in a
single entity - this will reduce write/read ops, but can introduce some limitations.
(See the docs -
or this related question)
The most common relationship would be a one(Customer) to many(Invoice) relationship,
which you can structure as below:
class Invoice(ndb.Model): #Child
invoice_id = ndb.StringProperty(required=True) #
class Customer(ndb.Model): #Parent
invoices = ndb.StructuredProperty(Invoices, repeated=True)
def get_invoice_by_id(self, invoice_id):
"""Returns a customer Invoice by invoice_id. Raises KeyError if invoice is not present."""
invoice_matches = [iv for iv in self.invoices if iv.invoice_id == invoice_id]
if not invoice_matches: raise KeyError("Customer has no Invoice with ID %s" % invoice_id)
return invoice_matches[0] # this could be changed to return all matches
Keep in mind the following restrictions of this implementation:
StructuredPropertys can not contain repeated properties inside of themselves.
The complexity for keeping invoice_id globally unique is going to be higher than if Invoice were in its own entity-group. (invoice_key.get() is always better than the query this requires))
You would need an instance method on Customer to find an Invoice by invoice_id.
You would need logic to prevent invoices with the same ID from existing on a single Customer
Here are some of the advantages:
You can query for Customer
Querying an Invoice by invoice_id will return the Customer instance, along with all invoices. (This could be a pro and a con, actually - you need logic to return the invoice from the customer)
Here is a more common solution, but by no means is it necessarily the "right solution."
This solution uses ancestor relationships, that allow you to keep writes to Invoice and
the related Customer atomic - so you could maintain aggregate invoice statistics on the
Customer level. (total_orders, total_gross, etc.)
class Invoice(ndb.Model):
customer = ndb.ComputedProperty(lambda self: self.key.parent(), indexed=False) # when not indexed, this is essentially a #property
class Customer(ndb.Model):
def get_invoice_by_id(self, invoice_id):
"""Returns a customer Invoice by invoice_id. Raises KeyError if invoice is not present."""
invoice_key = ndb.Key(Invoice._get_kind(), invoice_id, parent=self.key)
return invoice_key.get()
def query_invoices(self):
"""Returns ndb.Query for invoices by this Customer."""
return self.query(ancestor=self.key)
invoice = Invoice(parent=customer.key, **invoice_properties)
Good luck with Appengine! Once you get the hang of all of this, it is a truly rewarding platform.
Update:
Here is some additional code for transactionally updating customer aggregate totals as I mentioned above.
def create_invoice(customer_key, gross_amount_paid):
"""Creates an invoice for a given customer.
Args:
customer_key: (ndb.Key) Customer key
gross_amount_paid: (int) Gross amount paid by customer
"""
#ndb.transactional
def _txn():
customer = customer_key.get()
invoice = Invoice(parent=customer.key, gross_amount=gross_amount_paid)
# Keep an atomic, transactional count of customer aggregates
customer.total_gross += gross_amount_paid
customer.total_orders += 1
# batched put for API optimization
ndb.put_multi([customer, invoice])
return invoice
return _txn()
The above code works in a single entity group transaction (e.g. ndb.transactional(xg=False)) because Invoice is a child entity to Customer. If that connection is lost, you would need xg=True. (I'm not sure if it's more expensive, but it is less optimized)

Creating a django query that will retrieve the previous and next object based on alphabetical order

I have a django model that looks something like this:
class Definition
name = models.CharField(max_length=254)
text = models.TextField()
If I do the following query:
animal = Definition.objects.get(name='Owl')
and if I have the following definitions with these names in my database:
Elephant, Owl, Zebra, Human
is there a way to do a django query(ies) that will show me the previous and the next Definitions based on the animal object based on alphabetical order of the name field in the model?
I know that there are ways of getting previous/next based on datetime fields, but I am not so sure for this case.
I don't know of any way of doing this in less than three queries.
target = 'Owl'
animal = Definition.objects.get(name=target)
previous_animal = Definition.objects.order_by('name').filter(name__lt=target)[0]
next_animal = Definition.objects.order_by('name').filter(name__gt=target)[0]
If anyone comes across this like I just did...
heres my solution... it also loops(so if on last item it shows first item as next and if on first item shows last item as previous)
def get_previous_by_title(self):
curr_title = self.get_object().title
queryset = self.my_queryset()
try:
prev = queryset.filter(title__lt=curr_title).order_by("-title")[0:1].get()
except Video.DoesNotExist:
prev = queryset.order_by("-title")[0:1].get()
return prev
def get_next_by_title(self):
curr_title = self.get_object().title
queryset = self.my_queryset()
try:
next = queryset.filter(title__gt=curr_title).order_by("title")[0:1].get()
except Video.DoesNotExist:
next = queryset.order_by("title")[0:1].get()
return next
i have custom querysets based on user level so could just set the queryset as a normal queryset like... Video.objects.all() but anyplace I repeat code more than once I make a function

Categories

Resources