It is possible to force Granite to save empty strings as NULL in the DB?
I tried this code:
before_save :nilify_blanks
def nilify_blanks
self.to_h.keys.each do |column|
if self[column].is_a?(String) && self[column].empty?
self[column] = nil
end
end
end
But the compiler gives me an error:
223 | if self[column].is_a?(String) && self[column].empty?
^
Error: undefined method '[]' for Foo
Finally I used a macro:
class Pet < Granite::Base
connection pg
table pets
column id : Int64, primary: true
column name : String?
column breed : String?
column age : Int32?
timestamps
before_save :nilify_blanks
def nilify_blanks
{% for column in #type.instance_vars.select { |ivar| ivar.annotation(Granite::Column) } %}
{% if column.type.id == Union(String | Nil).id %}
col = self.{{column.name}}
if col && col.strip.empty?
self.{{column.name}} = nil
end
{% end %}
{% end %}
end
end
The macro will generate this code:
def nilify_blanks
col = self.name
if col && col.strip.empty?
self.name = nil
end
col = self.breed
if col && col.strip.empty?
self.breed = nil
end
...
end
Related
How can I get custom fields into getList function and add they to customer list in backend of opencart?
the following code is form getForm function
// Custom Fields
$this->load->model('customer/custom_field');
$data['custom_fields'] = array();
$custom_fields = $this->model_customer_custom_field->getCustomFields();
$confirmation_info = $this->model_sale_confirmation->getConfirmation($confirmation_id);
foreach ($custom_fields as $custom_field) {
$data['custom_fields'][] = array(
'custom_field_id' => $custom_field['custom_field_id'],
'custom_field_value' => $this->model_customer_custom_field->getCustomFieldValues($custom_field['custom_field_id']),
'name' => $custom_field['name'],
'value' => $custom_field['value'],
'type' => $custom_field['type'],
'location' => $custom_field['location'],
'sort_order' => $custom_field['sort_order']
);
}
$data['download'] = $this->url->link('tool/upload/download', 'user_token=' . $this->session->data['user_token'], true);
if (isset($this->request->post['custom_field'])) {
$data['confirmation_custom_field'] = $this->request->post['custom_field'];
} elseif (!empty($confirmation_info)) {
$data['confirmation_custom_field'] = json_decode($confirmation_info['custom_field'], true);
} else {
$data['confirmation_custom_field'] = array();
}
Custom field values are stored in Json format in oc_customer table custom_field column.
So you can get these values in getList() function and pass them in customers array like this
$data['customers'][] = array(
'account_custom_field'=>json_decode($result['custom_field'], true),
Now you need to get the name of the custom fields for displaying in customer list as columns of the table.
// Custom Fields
$this->load->model('customer/custom_field');
$data['custom_fields'] = array();
$custom_fields = $this->model_customer_custom_field->getCustomFields();
$confirmation_info = $this->model_sale_confirmation->getConfirmation($confirmation_id);
foreach ($custom_fields as $custom_field) {
$data['custom_fields'][] = array(
'custom_field_id' => $custom_field['custom_field_id'],
'custom_field_value' => $this->model_customer_custom_field->getCustomFieldValues($custom_field['custom_field_id']),
'name' => $custom_field['name'],
'value' => $custom_field['value'],
'type' => $custom_field['type'],
'location' => $custom_field['location'],
'sort_order' => $custom_field['sort_order']
);
}
You can create more columns in the table
{% for custom_field in custom_fields %}
<th>{{custom_field.name}}</th>
{% endfor %}
In the each customer row you can find the custom_field in customer's custom field data by matching id of custom field and then displaying the data in that particular column
These array will provide you the column names and then by matching custom_value_id from customer JSON Decode custom values you can display ..
{% for custom_field in custom__fields %}
{% if customer.account_custom_field[custom_field.custom_field_id] %}
{{customer.account_custom_field[custom_field.custom_field_id]}}
{% endif %}
{% endfor %}
There is some more effort required to display the column matching custom value in the right column so use the hint.
I've created a session list that contains my products, i need to update the quantity of any product by increasing it amount, for that am using an HTML type="number" , i also created a function which take the changed amount and multiplying it value with the current quantity, so lets say the amount of the first product by default is 2 by increasing the number lets say 2 the product amount will become 4 and so on, also the price will be multiplied .
Here are the codes:
<th style="text-align: center;" class="amount-izd">{{value["amount"]}}</th>
<th style="text-align: center; width: 14%;">
<div class="block">
<input type="number" id="myNumber" value="1" min=1 data-amount='{{value["amount"]}}' data-pros='{{value["id"]}}' data-price='
{% if g.currency == "euro" %}
{{format_price(value["price"] * config.SITE_CURRENCIES["euro"]).rsplit(".",1)[0]}}
{% elif g.currency == "dollar" %}
{{format_price(value["price"] * config.SITE_CURRENCIES["dollar"]).rsplit(".",1)[0]}}
{% else %}
{{format_price(value["price"] * config.SITE_CURRENCIES["ruble"]).rsplit(".",1)[0]}}
{% endif %}
'>
<label for="myNumber">qty</label>
</div>
</th>
JQuery codes:
$("input[type='number']").bind('keyup change click', function (e) {
if (! $(this).data("previousValue") ||
$(this).data("previousValue") != $(this).val()
)
{
var currentAmount = $(this).attr('data-amount');
var currentPrice = $(this).attr('data-price');
$(this).closest('tr').find('.amount-izd').text(parseInt(currentAmount) * $(this).val());
$(this).closest('tr').find('.price-izd').text(parseInt(currentPrice) * $(this).val());
$.ajax({
type: 'post',
url: '/standard-{{g.currency}}/profile/'+$(this).attr("data-pros")+'/update/price/' + parseInt(currentPrice) * $(this).val(),
cache: false
}).done(function(data){
if(data.error){
toastr.error(data.error)
}
});
$(this).data("previousValue", $(this).val());
} else {
}
});
And finally views.py :
#profile_route.route("/standard-<set_curr>/profile/cart/", methods=['GET','POST'])
#authorize
def cart_products():
if "cart" not in session:
return render_template("my-cart.html", display_cart = {}, total = 0)
else:
items = session["cart"]
dict_of_products = {}
total_price = 0
for item in items:
product = Goods.query.get(item)
total_price += product.price
if product.id in dict_of_products:
pass
else:
dict_of_products[product.id] = {"qty":1, "name":product.product_name, 'category':product.Category.name, "sizes": product.sizes, "hex_color":product.hex_color, "text_color":product.text_color, "material":product.material, "article":product.article, "price":product.price, "sort": product.sort, "amount": product.amount, 'slug':product.slug, 'public_id' : product.public_id, "id":product.id}
return render_template("my-cart.html", display_cart=dict_of_products, total = total_price)
#profile_route.route("/standard-<set_curr>/profile/<int:id>/update/price/<price>", methods=['GET','POST'])
#login_required
def update_price(id, price):
items = session["cart"]
dict_of_products = {}
for item in items:
product = Goods.query.get(item)
if product.id in dict_of_products:
dict_of_products[id]['price'] = price
return jsonify(success=dict_of_products[id]['price'])
return jsonify(error='No product found.')
If i changed the amount , in console i got a 500 error that says:
return jsonify(success=dict_of_products[id]['price'])
KeyError: 47
Please how to overcome this problem ?
Update:
I was wondering , is it possible to update any value of the dictionary by accessing it directly from JQuery ??
I have a view on my website that uses a couple of Django forms to allow the user to specify a date range. I was able to get it so that one Django form creates a start and end field and that when the user clicks on those fields a calendar widget (from here) pops up that allows the user to select a date range. However, when the user selects the date range and hits "apply" the form fields aren't updated.
EDIT
The form I'm using looks like this:
class DateRangeForm(forms.Form):
def __init__(self, *args, **kwargs):
initial_start_date = kwargs.pop('initial_start_date')
initial_end_date = kwargs.pop('initial_end_date')
required_val = kwargs.pop('required')
super(DateRangeForm,self).__init__(*args,**kwargs)
self.fields['start_date'].initial = initial_start_date
self.fields['start_date'].required = required_val
self.fields['end_date'].initial = initial_end_date
self.fields['end_date'].required = required_val
start_date = forms.DateField()
end_date = forms.DateField()
The view they are used in looks like this:
def table_search(request):
initial_start = "2015/2"
initial_end = "2015/222"
message = {'last_url':'table_search'}
if request.method == "POST":
daterange_form = DateRangeForm(request.POST,required=True,initial_start_date=initial_start,initial_end_date=initial_end)
else:
daterange_form = DateRangeForm(required=True,initial_start_date=initial_start,initial_end_date=initial_end)
search_dict.update({'daterange_form':daterange_form})
return render(request, 'InterfaceApp/table_search.html', search_dict)
The Django template here:
<div class="container">
<form action="/InterfaceApp/home/" method="post" class="form">
{% csrf_token %}
<div class="daterangepicker-container mcast-search-filter">
<div class="daterangepicker-label">Date range:</div>
<div id="daterange" class="daterangepicker-content">
{% bootstrap_form daterange_form %}
<i class="icon-calendar icon-large"></i>
</div>
</div>
</form>
</div>
<script>
// the start_date and end_date are the ids that django form fields created
$("#daterange").daterangepicker({
locale: {
format: 'YYYY-MM-DD'
},
startDate: '{{daterange_form.start_date.value}}',
endDate: '{{daterange_form.end_date.value}}'
});
</script>
EDIT 2
And the forms currently look like this (after #ShangWang suggestion) rendered:
Is there a way to display it so the start and end date fields show up? I tried changing the div class so it wasn't hidden, and then they showed up but seemed superfluous.
I use bootstrap-daterangepicker: https://github.com/dangrossman/bootstrap-daterangepicker. It would bind the widget's change to your django form field, so you don't need to manipulate the data once it comes to the views.py.
To get more details you should download and play with it, but here's a rough idea:
Your form.py:
class DateRangeForm(forms.Form):
start_date = forms.DateField()
end_date = forms.DateField()
def __init__(self, *args, **kwargs):
# initialize the start and end with some dates
Your template:
<div class="daterangepicker-container mcast-search-filter">
<div class="daterangepicker-label">Date range:</div>
<div id="daterange" class="daterangepicker-content">
<i class="icon-calendar icon-large"></i>
<span></span> <b class="caret"></b>
</div>
</div>
<!-- This is a hidden div that holds your form fields -->
<div class="hide">From {{ daterange_form.start_date }} to {{ daterange_form.end_date }}</div>
To trigger the widget you need a javascript binding:
// the id_start_date and id_end_date are the ids that django form fields created
$("#daterange").initDateRangePicker("#id_start_date", "#id_end_date");
I created a datepicker wrapper, and defined the initDateRangePicker function. You should put following code in a file called daterangepicker.js and import that in your template as well(or simply copy it into your template):
(function($) {
$.fn.initDateRangePicker = function(start_date_el, end_date_el, future) {
return this.each(function() {
var start = moment($(start_date_el).val());
var end = moment($(end_date_el).val());
var display_date = function(start, end) {
var str = ""
str += start.format('MMMM Do, YYYY');
str += " - ";
str += end.format('MMMM Do, YYYY');
return str;
};
$(this).find("span").html(display_date(start, end));
var self = this;
if(!future) {
$(this).daterangepicker({
format: 'YYYY-MM-DD',
timePicker: false,
ranges: {
'Last 7 days': [moment().subtract('days', 6), moment()],
'Month to date': [
moment().startOf('month'),
moment(),
],
'Last Month': [
moment().subtract('month', 1).startOf('month'),
moment().subtract('month', 1).endOf('month'),
]
},
}, function(start, end) {
$(start_date_el).val(start.format('YYYY-MM-DD'));
$(end_date_el).val(end.format('YYYY-MM-DD'));
$(self).find("span").html(display_date(start, end));
});
}
else {
$(this).daterangepicker({
format: 'YYYY-MM-DD',
timePicker: false,
ranges: {
'Next 7 days': [moment().add('days', 1), moment().add('days', 7)],
'Next month': [
moment().add('month', 1).startOf('month'),
moment().add('month', 1).endOf('month'),
],
},
}, function(start, end) {
$(start_date_el).val(start.format('YYYY-MM-DD'));
$(end_date_el).val(end.format('YYYY-MM-DD'));
$(self).find("span").html(display_date(start, end));
});
}
});
};
}).call(this, jQuery);
I have the following models set up
# task.rb
class Task << AR
# everything all task objects have in common
end
# login_request.rb
class Tasks::LoginRequest < Task
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s), constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now }, converter: Proc.new { |value| value.to_s.to_datetime }
end
I'm using the datetime_select helper in my form:
# _form.html.haml
= f.datetime_select :valid_until
This works quite well, but when I call update in my controller with the submitted form data I get the following error message:
1 error(s) on assignment of multiparameter attributes [error on assignment [2014, 4, 2, 9, 48] to valid_until (can't write unknown attribute 'expires_at')]
So, I'm guessing the updated method tries to manipulate the attributes hash directly, but obviously it can't find the attribute expires_at, since it's a simple accessor method of the JSON column data.
I know I could simply add this field to the DB and it would probably work - although there's no need then to have a composed_of statement. But I'd rather not go this route, because not every task has a expires_at column.
How can I overcome this error? Or did I miss something?
Currently compose_of is not supporting this scenario since it writes directly to attributes that are assumed to be in the database. I wrote a tweaked compose_of version that does (based of Rails 4.0.2 version)
Putting this in initialize folder:
#/initialize/support_store_in_composed_of.rb
module ActiveRecord
module Aggregations
extend ActiveSupport::Concern
def clear_aggregation_cache #:nodoc:
#aggregation_cache.clear if persisted?
end
module ClassMethods
def composed_of_with_store_support(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter, :store)
name = part_id.id2name
class_name = options[:class_name] || name.camelize
mapping = options[:mapping] || [ name, name ]
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor, options[:store])
writer_method(name, class_name, mapping, allow_nil, converter, options[:store])
create_reflection(:composed_of, part_id, nil, options, self)
end
private
def reader_method(name, class_name, mapping, allow_nil, constructor, store=nil)
define_method(name) do
if #aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
if store.present?
attrs = mapping.collect {|pair| send(pair.first)}
else
attrs = mapping.collect {|pair| read_attribute(pair.first)}
end
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
#aggregation_cache[name] = object
end
#aggregation_cache[name]
end
end
def writer_method(name, class_name, mapping, allow_nil, converter, store=nil)
define_method("#{name}=") do |part|
klass = class_name.constantize
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
if part.nil? && allow_nil
mapping.each { |pair| self[pair.first] = nil }
#aggregation_cache[name] = nil
else
if store.present?
mapping.each { |pair| send("#{pair.first}=", part.send(pair.last)) }
else
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
end
#aggregation_cache[name] = part.freeze
end
end
end
end
end
end
And using it like this would solve your problem.
class Task < ActiveRecord::Base
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of_with_store_support :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s),
constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now },
converter: Proc.new { |value| value.to_s.to_datetime },
store: true
end
I am using an unordered_list tag in django.
I have the following list:
foo = ['A', ['B', 'C', 'D'], 'E']
And the following tag:
{{ foo|unordered_list }}
Which produces, as expected the following:
<li>A
<ul>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>
</li>
<li>E</li>
What I would like to do is to add id and class properties to each "li" node.
I understand that I can do it in JavaScript, or implement my own template tag in django. But I wanted to ask first. May be there already exists an easy build-in way to do it in django template?
As you can see in the source, the <li> is hardcoded so you can't do it without creating your own templatefilter.
def unordered_list(value, autoescape=None):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
The list is assumed to be in the proper format. For example, if ``var``
contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
then ``{{ var|unordered_list }}`` would return::
<li>States
<ul>
<li>Kansas
<ul>
<li>Lawrence</li>
<li>Topeka</li>
</ul>
</li>
<li>Illinois</li>
</ul>
</li>
"""
if autoescape:
from django.utils.html import conditional_escape
escaper = conditional_escape
else:
escaper = lambda x: x
def convert_old_style_list(list_):
"""
Converts old style lists to the new easier to understand format.
The old list format looked like:
['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
And it is converted to:
['Item 1', ['Item 1.1', 'Item 1.2]]
"""
if not isinstance(list_, (tuple, list)) or len(list_) != 2:
return list_, False
first_item, second_item = list_
if second_item == []:
return [first_item], True
old_style_list = True
new_second_item = []
for sublist in second_item:
item, old_style_list = convert_old_style_list(sublist)
if not old_style_list:
break
new_second_item.extend(item)
if old_style_list:
second_item = new_second_item
return [first_item, second_item], old_style_list
def _helper(list_, tabs=1):
indent = u'\t' * tabs
output = []
list_length = len(list_)
i = 0
while i < list_length:
title = list_[i]
sublist = ''
sublist_item = None
if isinstance(title, (list, tuple)):
sublist_item = title
title = ''
elif i < list_length - 1:
next_item = list_[i+1]
if next_item and isinstance(next_item, (list, tuple)):
# The next item is a sub-list.
sublist_item = next_item
# We've processed the next item now too.
i += 1
if sublist_item:
sublist = _helper(sublist_item, tabs+1)
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
indent, indent)
output.append('%s<li>%s%s</li>' % (indent,
escaper(force_unicode(title)), sublist))
i += 1
return '\n'.join(output)
value, converted = convert_old_style_list(value)
return mark_safe(_helper(value))
unordered_list.is_safe = True
unordered_list.needs_autoescape = True
It should be fairly easy to rewrite the filter to add id/class support though, depending on how you want it to work.