Django Forms, ModelChoiceField using RadioSelect widget, Grouped by FK - django-forms

The challenge, output a radio select in nested <ul></ul>, grouped by the task fk.
ie.
class Category(models.Model):
# ...
class Task(models.Model):
# ...
category = models.ForeignKey(Category)
# ...
forms.py
class ActivityForm(forms.ModelForm):
# ...
task = forms.ModelChoiceField(
queryset = Task.objects.all(),
widget = RadioSelectGroupedByFK
)
widgets.py
class RadioFieldRendererGroupedByFK(RadioFieldRenderer):
"""
An object used by RadioSelect to enable customization of radio widgets.
"""
#def __init__(self, attrs=None):
# Need a radio select for each?? Just an Idea.
#widgets = (RadioSelect(attrs=attrs), RadioSelect(attrs=attrs))
#super(RadioFieldRendererGroupedByFK, self).__init__(widgets, attrs)
def render(self):
"""Outputs nested <ul> for this set of radio fields."""
return mark_safe(
#### Somehow the crux of the work happens here? but how to get the
#### right context??
u'<ul>\n%s\n</ul>' % u'\n'.join(
[u'<li>%s</li>' % force_unicode(w) for w in self]
)
)
class RadioSelectGroupedByFK(forms.RadioSelect):
renderer = RadioFieldRendererGroupedByFK
Best thanks!!

itertools.groupby() is perfect for this. I'll assume that Task and Category each have a name attribute you want them sorted by. I don't know the Django APIs, so you'll probably want to move sorting into the db query, and you'll need to look at the widget's attributes to figure out how to access the task objects, but here's the basic formula:
from itertools import groupby
# sort first since groupby only groups adjacent items
tasks.sort(key=lambda task: (task.category.name, task.name))
for category, category_tasks in groupby(tasks, key=lambda task: task.category):
print '%s:' % category.name
for task in category_tasks:
print '* %s' % task.name
This should print a list like:
Breakfast foods:
* eggs
* spam
Dinner foods:
* spam
* spam
* spam
HTH

Related

How should I change his script so that it works for collections and not just one product in Shopify?

The script is a template that comes with the script editor app in Shopify. I need to make it work so that if you buy one product from a collection, you get another one free from that collection. This script works only for buying the same product. Here is the script:
PAID_ITEM_COUNT = 2
DISCOUNTED_ITEM_COUNT = 1
# Returns the integer amount of items that must be discounted next
# given the amount of items seen
#
def discounted_items_to_find(total_items_seen, discounted_items_seen)
Integer(total_items_seen / (PAID_ITEM_COUNT + DISCOUNTED_ITEM_COUNT) * DISCOUNTED_ITEM_COUNT) - discounted_items_seen
end
# Partitions the items and returns the items that are to be discounted.
#
# Arguments
# ---------
#
# * cart
# The cart to which split items will be added (typically Input.cart).
#
# * line_items
# The selected items that are applicable for the campaign.
#
def partition(cart, line_items)
# Sort the items by price from high to low
sorted_items = line_items.sort_by{|line_item| line_item.variant.price}.reverse
# Create an array of items to return
discounted_items = []
# Keep counters of items seen and discounted, to avoid having to recalculate on each iteration
total_items_seen = 0
discounted_items_seen = 0
# Loop over all the items and find those to be discounted
sorted_items.each do |line_item|
total_items_seen += line_item.quantity
# After incrementing total_items_seen, see if any items must be discounted
count = discounted_items_to_find(total_items_seen, discounted_items_seen)
# If there are none, skip to the next item
next if count <= 0
if count >= line_item.quantity
# If the full item quantity must be discounted, add it to the items to return
# and increment the count of discounted items
discounted_items.push(line_item)
discounted_items_seen += line_item.quantity
else
# If only part of the item must be discounted, split the item
discounted_item = line_item.split(take: count)
# Insert the newly-created item in the cart, right after the original item
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, discounted_item)
# Add it to the list of items to return
discounted_items.push(discounted_item)
discounted_items_seen += discounted_item.quantity
end
end
# Return the items to be discounted
discounted_items
end
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && product.id == 11380899340
end
discounted_line_items = partition(Input.cart, eligible_items)
discounted_line_items.each do |line_item|
line_item.change_line_price(Money.zero, message: "Buy one Bolur, get one Bolur free")
end
Output.cart = Input.cart
I tried changing, what seems to be the relevant code:
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && product.id == 11380899340
end
to this:
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && **collection.id** == 123
end
but I get an error:
undefined method 'collection' for main (Your Cart)
undefined method'collection' for main (No Customer)
Two things here:
line_item.variant.product does not have the property collections. For that, you want to use line_item.product (docs) – which (should...see point two) expose all of the methods and properties of the product object.
However, in my attempt to do something similar to you (discount based on product) I tried iterating over line_item.variant – and am always hitting the error of undefined method 'product' for #<LineItem:0x7f9c97f9fff0>. Which I interpret as "line_items accessed in cart scripts can only be at the variant level".
So, I wonder if this is because the cart only contains variants (product/color/size) – so we aren't actually able to access the line_items by product, and only by variant.
I tired iterating over line_item.product_id, which also throws a similar error. I think we just have to try to do some hacky thing at the variant level.
I am going to see if I can access the product by the variant ID...back to the docs!
You actually can't do a collection, so you'd need to modify the script to work with a product type or tags. That script will need to be heavily modified to work for a number of products and not multiples of the same

Both sql and python constraints are not working in odoo 9

I have been battling with this for a while. none of the two options are working neither were they giving errors. i commented the pythonic constrain method for you to see.
code snippet:
class house_development(models.Model):
_name = 'house.development'
_description = 'Development'
_inherit = ["mail.thread"]
name = fields.Char(string="Name", required=True, track_visibility='onchange')
description = fields.Text(string="Description", track_visibility='onchange')
# #api.one
# #api.constrains('name')
# def _identify_same_name(self):
# for record in self:
# if record.name in self:
# raise exceptions.ValidationError("There is another development/project with the same name: %s" % record.name)
_sql_constraints = [
('name_unique',
'UNIQUE(name)',
"There is another development/project with the same name"),
]
It should be like that,
#api.multi
#api.constrains('name')
def _identify_same_name(self):
for record in self:
obj = self.search([('name','=ilike',record.name),('id','!=',record.id)])
if obj:
raise exceptions.ValidationError("There is another development/project with the same name: %s" % record.name)
You need to search for the same name but not with the same id.
And for database unique constrains you may add like that.
_sql_constraints = [
('name_unique', 'unique(name)', 'There is another development/project with the same name!!!'),
]
Database constrains will not be added if there are duplicate name in
table. Sql constrains will apply only if the rule is not violated by
the existing data. I think in your case that's the point with Sql
constrains. And make sure for that you need to upgrade module. first
check duplicate records in database by firing that query.
Select name, count(*) from table_name
group by name
having count(*) > 1;
Available operators in domain in odoo Click to see more.

Scrapy unable to scrape items, xpath not working

I spend lot of time trying to scrape information with scrapy without sucess.
My goal is to surf through category and for each item scrape title,price and title's href link.
The problem seems to come from the parse_items function. I've check xpath with firepath and I'm able to select the items as wanted, so maybe I just don't catch how xpath are processed by scrapy...
Here is my code
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy.selector import Selector
from ..items import electronic_Item
class robot_makerSpider(CrawlSpider):
name = "robot_makerSpider"
allowed_domains = ["robot-maker.com"]
start_urls = [
"http://www.robot-maker.com/shop/",
]
rules = (
Rule(LinkExtractor(
allow=(
"http://www.robot-maker.com/shop/12-kits-robots",
"http://www.robot-maker.com/shop/36-kits-debutants-arduino",
"http://www.robot-maker.com/shop/13-cartes-programmables",
"http://www.robot-maker.com/shop/14-shields",
"http://www.robot-maker.com/shop/15-capteurs",
"http://www.robot-maker.com/shop/16-moteurs-et-actionneurs",
"http://www.robot-maker.com/shop/17-drivers-d-actionneurs",
"http://www.robot-maker.com/shop/18-composants",
"http://www.robot-maker.com/shop/20-alimentation",
"http://www.robot-maker.com/shop/21-impression-3d",
"http://www.robot-maker.com/shop/27-outillage",
),
),
callback='parse_items',
),
)
def parse_items(self, response):
hxs = Selector(response)
products = hxs.xpath("//div[#id='center_column']/ul/li")
items = []
for product in products:
item = electronic_Item()
item['title'] = product.xpath(
"li[1]/div/div/div[2]/h2/a/text()").extract()
item['price'] = product.xpath(
"div/div/div[3]/div/div[1]/span[1]/text()").extract()
item['url'] = product.xpath(
"li[1]/div/div/div[2]/h2/a/#href").extract()
#check that all field exist
if item['title'] and item['price'] and item['url']:
items.append(item)
return items
thanks for your help
The xpaths in your spider are indeed faulty.
Your first xpath for products does work but it's not explicit enough and might fail really easily. While the product detail xpaths are not working at all.
I've got it working with:
products = response.xpath("//div[#class='product-container']")
items = []
for product in products:
item = dict()
item['title'] = product.xpath('.//h2/a/text()').extract_first('').strip()
item['url'] = product.xpath('.//h2/a/#href').extract_first()
item['price'] = product.xpath(".//span[contains(#class,'product-price')]/text()").extract_first('').strip()
All modern websites have very parsing friendly html sources (since they need to parse it themselves for their fancy css styles and javascript functions).
So generally you should look at class and id names of nodes you want to extract with browser inspect tools (right click -> inspect element) instead of using some automated selection tool. it's more reliable and doesn't take much more work once you get the hang of it.

trying to select a User in a TextInput in ModelForm in Django - and it won't work

I'm trying to create a webapp in django 1.9 for task tracking and ordering. The different tasks are divided into spaces (like different projects). Now, I want to be able to choose what the task is assigned to in the CreateView.
The problem is, that I have a large number of users in my system, so I do not want to show a dropdown. Instead, I want to use a TextInput widget, to have the form check for the available options (this way I can also use typeahead on the client side).
This is the best I could come up with for the TaskCreate view:
class TaskCreate(LoginRequiredMixin, CreateView):
"""
a view for creating new tasks
"""
model = Task
fields = ['space', 'name', 'description', 'assigned_to', 'due_date']
template_name = "task_tracker/task_form.html"
success_url = reverse_lazy('tracker:my_open_task_list')
def get_context_data(self, **kwargs):
context = super(TaskCreate, self).get_context_data(**kwargs)
context['header_caption'] = 'Create'
context['submit_caption'] = 'Create'
context['all_usernames'] = [x.username for x in User.objects.all()]
return context
def get_form(self, form_class=None):
form = super(TaskCreate, self).get_form(form_class)
form.fields['assigned_to'].choices = [(x.username, x.id) for x in User.objects.all()]
form.fields['assigned_to'].initial = self.request.user.username,
form.fields['assigned_to'].widget = widgets.TextInput()
try:
form.fields['space'].initial = Space.objects.get(name=self.request.GET['space'])
finally:
return form
def form_valid(self, form):
form.instance.created_by = self.request.user
form.instance.assigned_to = User.objects.get(username=form.cleaned_data['assigned_to'])
return super(TaskCreate, self).form_valid(form)
But the thing is that this is not working - the form still considers my choice to be illegal, even when I type in a valid username.
I tried to switch places the x.username and x.id in the choice field but it didn't help me.
I'm stuck on this for a week now. Can anybody help me please?

Sorting by column

I try to do something that can certainly be done easily but I can find a way.
I have a QtreeView displaying a QFileSystemModel that I custumized with two additionnal columns (sections). I work with directories named by date (YYYYMMDDHHMMSS+num). 'num' at the end of the string is a reference (an integer,ex: 04 or 12 or 53 ....) that can be similar to other directory name. 'num' is displayed in a fourth column.
I would like to group all directories with the similar references (for exemple in an ascending order) and also sort the directories by name (date) within each group.
Can you give me a hand please.
Folders looks like this:
201307
2013072400000053
2013072500000006
2013072600000053
2013072700000006
2013072800000006
2013072900000053
2013073000000006
2013073100000057
201308
2013082400000006
2013082500000053
2013082600000053
2013082700000057
2013082800000006
2013082900000057
2013083000000006
2013083100000053
...
code :
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
rootpathsource = " "
class MyFileSystemModel(QtGui.QFileSystemModel):
def columnCount(self, parent = QtCore.QModelIndex()):
# Add two additionnal columns for status and Instrument number
return super(MyFileSystemModel, self).columnCount() + 1
def headerData(self, section, orientation, role):
# Set up name for the two additionnal columns
if section == 4 and role == QtCore.Qt.DisplayRole :
return 'Number ref'
else:
return super(MyFileSystemModel, self).headerData(section, orientation, role)
def data(self, index, role):
if index.column() == 4: #if ref
ind = index.parent()
parentString = ind.data().toString()
if parentString in self.fileInfo(index).fileName() and self.fileInfo(index).isDir() == True and role == QtCore.Qt.DisplayRole:
return self.fileInfo(index).fileName()[-2:] # take the last two digits
else:
return super(MyFileSystemModel, self).data(index, role)
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignLeft
class TreeViewUi(QtGui.QWidget):
def __init__(self, parent=None):
super(TreeViewUi, self).__init__(parent)
self.model = MyFileSystemModel(self)
self.model.setRootPath(rootpathsource)
self.indexRoot = self.model.index(self.model.rootPath())
self.treeView = QtGui.QTreeView(self)
self.treeView.setExpandsOnDoubleClick(False)
self.treeView.setModel(self.model)
self.treeView.setRootIndex(self.indexRoot)
self.treeView.setColumnWidth(0,300)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.treeView)
class MainGui(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainGui,self).__init__(parent)
#QTreeView widget for files selection
self.view = TreeViewUi()
self.setCentralWidget(self.view)
self.resize(600,700)
def main():
main = MainGui()
main.show()
return main
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
StartApp = main()
sys.exit(app.exec_())
The way to do this is to use the QSortFilterProxyModel class.
All you have to do is create a subclass of it and reimplement its lessThan method. This method has two QModelIndex arguments which can be compared in any way you like before returning either True or False. In your case, you would first check which column was being sorted, and just return the value of the base-class lessThan method for the "standard" columns. But for the "Number Ref" column, you would first compare numerically, and then if that was equal, compare the values of the "Name" column for the same row.
To install the sort-filter proxy, you just need to do:
self.sortFilter = MyCustomSortFilterProxy(self)
self.sortFilter.setSourceModel(self.model)
self.treeView.setModel(self.sortFilter)

Resources