Django Crispy Form - horizontal form - how to change column widths - django-forms

I have a horizontally oriented crispy form and the label widths on my form vary a lot. The current effect is either not very wide columns with bunched up labels or a lot of white space between the label and the response area. It'd be nice if the space for a label better reflects its individual need. Most of the form are y/n questions with sometimes long labels.
Also, I defined a horizontal radio button that works great if I don't use crispy, but with crispy, the buttons are stacked. Any ideas? Thanks.
Here's my forms.py:
class HorizontalRadioSelect(forms.RadioSelect):
template_name = 'horizontal_select.html'
class OptionalServiceAndDemographicsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OptionalServiceAndDemographicsForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.attrs['autocomplete'] = 'off'
self.helper.form_id = 'id-gather-name'
self.helper.form_class = 'form-horizontal'
self.helper.form_method = 'post'
# self.helper.form_action = ''
self.helper.label_class = 'col-4'
self.helper.field_class = 'col-2'
self.helper.form_tag = False
self.helper.layout = Layout(
'public_assistance',
Row(Column('dependents'),Column('single_parent')),
'country_of_birth',
Row(Column('immigrant'),Column('us_citizen')),
Row(Column(''),Column('plans_to_stay')),
'disabilities_not_required_disclose',
'disabilities_student_wants_to_disclose',
'disabilities_student_understands',
'disabilities_student_requests',
)
class Meta:
model = OptionalServiceAndDemographics
fields = ['public_assistance',
'dependents',
'single_parent',
'country_of_birth',
'immigrant',
'us_citizen',
'plans_to_stay',
'disabilities_not_required_disclose',
'disabilities_student_wants_to_disclose',
'disabilities_student_understands',
'disabilities_student_requests',
'disability_vision_observed',
'disability_vision_documented',
'disability_blind_observed',
'disability_blind_documented',
'disability_hearing_observed',
'disability_hearing_documented',
'disability_deaf_observed',
'disability_deaf_documented',
'disability_speech_observed',
'disability_speech_documented',
'disability_mute_observed',
'disability_mute_documented',
'disability_paralysis_observed',
'disability_paralysis_documented',
'disability_ortho_observed',
'disability_ortho_documented',
'disability_extremity_observed',
'disability_extremity_documented',
'disability_learning_observed',
'disability_learning_documented',
'referral',
'last_program_name',
'last_program_location',
'enrolled_in_other_program',
'enrolled_in_other_hse',
'current_other_program_name',
'current_other_program_location'
]
labels = { 'dependents': 'Dependents (children under 18 years of age):',
'single_parent': 'Single parent or guardian:',
'us_citizen': 'US Citizen:',
'plans_to_stay': 'Plans to stay in USA:',
'disabilities_not_required_disclose': 'Student understands they are not required to disclose disability.',
'disabilities_student_wants_to_disclose': 'Student wishes to disclose a disability.',
'disabilities_student_understands': 'Student understands self-disclosing a disability makes student eligible for reasonable accommodations.',
'disabilities_student_requests': 'Student requested an accommodation.',
'referral': 'Referred by or heard about the program from source:',
'last_program_name': 'Program Name:',
'last_program_location': 'Location',
'enrolled_in_other_program': 'Student enrolled in other reading program',
'enrolled_in_other_hse': 'Student enrolled in other HSE program',
'current_other_program_name': 'Program Name:',
'current_other_program_location': 'Contact Info or location',
'disability_vision_observed': 'observed',
'disability_vision_documented': 'documented',
'disability_blind_observed': 'observed',
'disability_blind_documented': 'documented',
'disability_hearing_observed': 'observed',
'disability_hearing_documented': 'documented',
'disability_deaf_observed': 'observed',
'disability_deaf_documented': 'documented',
'disability_speech_observed': 'observed',
'disability_speech_documented': 'documented',
'disability_mute_observed': 'observed',
'disability_mute_documented': 'documented',
'disability_paralysis_observed': 'observed',
'disability_paralysis_documented': 'documented',
'disability_ortho_observed': 'observed',
'disability_ortho_documented': 'documented',
'disability_extremity_observed': 'observed',
'disability_extremity_documented': 'documented',
'disability_learning_observed': 'Sobserved',
'disability_learning_documented': 'documented',
}
yes_no = [('True', 'Yes'), ('False', 'No')]
widgets = {
'dependents': HorizontalRadioSelect(choices=yes_no),
'single_parent': HorizontalRadioSelect(choices=yes_no),
'immigrant': HorizontalRadioSelect(choices=yes_no),
'us_citizen': HorizontalRadioSelect(choices=yes_no),
'plans_to_stay': HorizontalRadioSelect(choices=yes_no),
'disabilities_not_required_disclose': HorizontalRadioSelect(choices=yes_no),
'disabilities_student_wants_to_disclose': HorizontalRadioSelect(choices=yes_no),
'disabilities_student_understands': HorizontalRadioSelect(choices=yes_no),
'disabilities_student_requests': HorizontalRadioSelect(choices=yes_no),
}
horizontal_select.html: (courtesy of another stackoverflow answer)
{% with id=widget.attrs.id %}
<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>
{% for group, options, index in widget.optgroups %}
{% if group %}
<li>{{ group }}
<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>
{% endif %}
{% for option in options %}
<li style="display: inline-block">{% include option.template_name with widget=option %}</li>
{% endfor %}
{% if group %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
{% endwith %}

Related

Pass string as variable name to for loop with nunjucks

I have different sites, with different sitenames and try to get the right array on each site. So basically I want to pass the variable name dynamically to the for loop.
{% set sitename = "user" %}
{% set blockRef = sitename + 'Blocks' %} //result should be userBlocks
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% for blockName in blockRef %}
//user values from userBlocks array here
{% endfor %}
However, the passed name is interpreted as text and does not refer to the given array. Is there a way to make my code dynamic?
{% set sitename = "user" %}
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% set blocks = userBlocks if sitename == 'user' else adminBlocks %}
{% for blockName in blocks %}
//user values from userBlocks array here
{% endfor %}

How to compare date in nunjucks?

So I have an array object.
var abc = [{ "title": "abc", "validUntil": "9/7/2019"];
I'm not sure how to compare date in nunjucks. I also think this can be done in the loop itself.
<div>
{% for a in abc %}
{% if new Date(offer.validUntil) > new Date() %}
{{a.title}}
{% endif %}
{% endfor %}
</div>
You can define a global function toDate
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
// returns `now` if no argument is passed
env.addGlobal('toDate', function(date) {
return date ? new Date(date) : new Date();
});
var html = env.renderString(`
<div>
{% for offer in offers %}
{% if toDate(offer.validUntil) > toDate() %}
{{offer.title}}
{% endif %}
{% endfor %}
</div>
`,
{
offers: [
{title: 'Some offer title', validUntil: '9/7/2019'},
{title: 'Another offer title', validUntil: '1/6/2019'}
]
});
console.log(html);
The another way is to define a custom filter isActual
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
env.addFilter('isActual', function(offers) {
return offers.filter(offer => new Date(offer.validUntil) > new Date());
});
var html = env.renderString(
`
<div>
{% for offer in offers | isActual %}
{{offer.title}}
{% endfor %}
</div>
`,
{
offers: [
{title: 'Some offer title', validUntil: '9/7/2019'},
{title: 'Another offer title', validUntil: '1/6/2019'}
]
});
console.log(html);
P.S. Pass date is a string like 9/7/2019 is a bad idea. The date intepretation (dd.mm.yyyy or mm.dd.yyyy) depends on browser setting. I recommend to use unix-epoch: new Date().getTime().

Custom pagination in OctoberCMS

I have a code to do query from form:
Painting::where('type',input("type"))->where('material',input("material"))->whereHas('artist', function($q)
{
$q->where('artist_slug', '=', $this->param('slug'));
})->paginate(15);
How can I do custom pagination with page numbers list? Or maybe dinamicaly loading
Check out the RainLab Blog Plugin it has a good example, also see here .
But here is a more complete example if you want to add URL friendly paginations paintings/2 where 2 is the page number and/or handle URL parameters paintings/2?type=something&material=something
In your Painting Model add a scope for listing paintings on the front-end :
public function scopeListPaintings($query, $options)
{
extract(array_merge([
'page' => 1,
'perPage' => 30,
'material' => null,
'type' => null,
'artistSlug' => null,
], $options));
if( !empty($artistSlug) ){
$query->whereHas('artist', function($q)
{
$q->where('artist_slug', $artistSlug );
});
}
if( !empty($material) ){
$query->where( 'material' , $material)
}
if( !empty($type) ){
$query->where( 'type' , $type)
}
return $query->paginate( $perPage, $page );
}
Then in your Painting Component Define the properties and call the previous scope ;
public $paintings ;
public $pageNumber;
public $perPage;
public $urlParams;
public function defineProperties()
{
return [
'pageNumber' => [
'title' => 'Page #',
'description' => 'Paintings Page #',
'type' => 'string',
'default' => '{{ :page }}',
],
'perPage' => [
'title' => 'Paintings per page',
'type' => 'string',
'default' => '30',
]
.. ect make sure to add it to your page markup
];
}
private function propertyOrParam($name, $default = null)
{
$value = $this->property($name, $default);
if (substr($value, 0, 1) == ':')
return $this->param($value, $default);
return $value;
}
public function getPaintings()
{
/** Pagination */
$this->pageNumber = $this->propertyOrParam('pageNumber') ;
$this->perPage = $this->propertyOrParam('perPage');
/** Url Params if exist */
$params = Request::query()
$this->page['urlParams'] = $this->urlParams = !empty( $params ) ? http_build_query( $params ) : null ;
return (new Painting)->ListPaintings([
'page' => $this->pageNumber,
'perPage' => $this->perPage,
'type' => input('type'),
'material' => input('material'),
'artistSlug' => $this->propertyOrParam('slug')
]);
}
public function onRun()
{
$this->page['paintings'] = $this->paintings = $this->getPaintings() ;
// Redirect to Last page if page # not found in request
if ($this->pageNumber > $this->paintings->lastPage() && $this->pageNumber > 1){
return Redirect::to($this->currentPageUrl([ $this->paramName('pageNumber') => $this->paintings->lastPage().$this->urlParams ]));
}
}
Then your can create a global partial in your theme to handle paginations - You can reuse it all over your site - add the following snippet ( Borrowed from the Forum Plugin ) ;
Pagination.htm
{% set paginationEnabled =
records.currentPage > 1 or
records.lastPage > 1 or
records.lastPage > records.currentPage
%}
{% if paginationEnabled %}
{# How many pages to display around the current page #}
{% set n = 2 %}
{% set currentPageZeroBased = records.currentPage-1 %}
{% set pageLinks = [] %}
{% set pageSet = [] %}
{% set startOffset = max(currentPageZeroBased - n, 0) %}
{% if (startOffset + 2*n+1) > (records.lastPage-1) %}
{% set startOffset = max(records.lastPage - 2*n - 1, 0) %}
{% endif %}
{% for page in 1..records.lastPage %}
{% set pageLinks = pageLinks|merge([page]) %}
{% endfor %}
{% set activeBlock = pageLinks|slice(startOffset, 2*n + 1) %}
{% if startOffset > 0 %}
{% set pageSet = pageSet|merge([1]) %}
{% if startOffset > 1 %}
{% set pageSet = pageSet|merge(['...']) %}
{% endif %}
{% endif %}
{% set pageSet = pageSet|merge(activeBlock) %}
{% set diffToEnd = (records.lastPage-1) - (startOffset + 2*n+1) + 1 %}
{% if diffToEnd > 0 %}
{% if diffToEnd > 1 %}
{% set pageSet = pageSet|merge(['...']) %}
{% endif %}
{% set pageSet = pageSet|merge([records.lastPage]) %}
{% endif %}
<div>
<div>
<div>
<div>
<span>Records <b>{{records.firstItem|trim}} - {{records.lastItem|trim}}</b> Of {{ records.total|number_format(0, '.', ',')}}</span>
<span>Page {{ records.currentPage }} of {{ records.lastPage }}</span>
</div>
</div>
</div>
<div>
<ul>
{% if records.currentPage > 1 %}
<li>
<i class="fa fa-angle-left"></i>
</li>
{% endif %}
{% for page in pageSet %}
{% if page == '...' %}
<li>
{{ page }}
</li>
{% else %}
<li class="{{ page == records.currentPage ? 'active' }}">
{{ page }}
</li>
{% endif %}
{% endfor %}
{% if records.lastPage > records.currentPage %}
<li>
<i class="fa fa-angle-right"></i>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
Then in your page or component ;
{% for painting in paintings %}
....
{% endfor %}
// Add the pagination
{% partial "pagination" records=paintings %}

Format liquid(Shopify) code in visual studio code

How to format .liquid (Shopify liquid) code in Visual Studio.
By settings language as HTML I can do it but at the same time, I can't use Shopify autocomplete. When I switch to liquid.html then I can use the autocomplete but I can't format code. Is there any way I can use another language and format code as another language in visual studio?
The VSCode Liquid extension provides formatting and syntax highlighting. Also has intellisense and ton of other features.
<div class="page-width">
{% if section.settings.title != blank %}
<header class="section-header">
<h2 class="section-header__title">
{{section.settings.title}}
</h2>
</header>
{% endif %}
<div class="popup-gallery">
{%- for media in product.media -%}
{%- liquid
assign has_video = false
assign video_type = ''
case media.media_type
when 'external_video'
assign has_video = true
assign video_type = media.host
if media.host contains 'youtube'
assign video_id = media.external_id
endif
when 'video'
assign has_video = true
assign video_type = 'mp4'
endcase -%}
<div
href="{%- if has_video -%}
{%- if media.media_type=='video' -%}
{%- for source in media.sources -%}
{{- source.url -}}
{%-endfor-%}
{%- else -%}
{%- assign video_url = media | external_video_url -%}
{%- if video_url contains "youtube" -%}
https://www.youtube.com/watch?v={{- media.external_id -}}
{%- else -%}
https://vimeo.com/{{- media.external_id -}}
{%- endif -%}
{%- endif -%}
{%- else -%}
{{- media | image_url -}}
{%- endif -%}"
class="
{% if has_video %}
video
{% else %}
image
{% endif %}"
title="
{% if has_video %}
This is a video
{% else %}
This is a image
{% endif %}
">
{%- assign img_url = media.preview_image | img_url: '1x1' | replace: '_1x1.', '_{width}x.' -%}
<img
class="lazyload"
data-src="{{ img_url }}"
data-widths="[120, 360, 540, 720]"
data-aspectratio="{{ media.preview_image.aspect_ratio }}"
data-sizes="auto"
alt="GALLERY"
>
<noscript>
<img
class="lazyloaded"
src="{{ media | img_url: '400x' }}"
alt="GALLERY"
>
</noscript>
</div>
{%- endfor -%}
</div>
</div>
{{ 'magnific-popup.min.css' | asset_url | stylesheet_tag }}
<script
type="text/javascript"
src="{{ 'jquery.min.js' | asset_url }}"
></script>
<script
type="text/javascript"
src="{{ 'magnific-popup.min.js' | asset_url }}"
></script>
<script type="text/javascript">
$(".popup-gallery").magnificPopup({
delegate: "div",
type: "image",
gallery: {
enabled: true,
navigateByImgClick: true,
preload: [0, 1] // Will preload 0 - before current, and 1 after the current image
},
callbacks: {
elementParse: function (item) {
console.log(item.el[0].className);
if (item.el[0].className == "video") {
(item.type = "iframe"),
(item.iframe = {
patterns: {
youtube: {
index: "youtube.com/", // String that detects type of video (in this case YouTube). Simply via url.indexOf(index).
id: "v=", // String that splits URL in a two parts, second part should be %id% // Or null - full URL will be returned // Or a function that should return %id%, for example:
// id: function(url) { return 'parsed id'; }
src: "//www.youtube.com/embed/%id%?autoplay=1" // URL that will be set as a source for iframe.
},
vimeo: {
index: "vimeo.com/",
id: "/",
src: "//player.vimeo.com/video/%id%?autoplay=1"
},
gmaps: {
index: "//maps.google.",
src: "%id%&output=embed"
}
}
});
} else {
(item.type = "image"),
(item.tLoading = "Loading image #%curr%..."),
(item.mainClass = "mfp-img-mobile"),
(item.image = {
tError: 'The image #%curr% could not be loaded.'
});
}
}
}
});
</script>
{% schema %}
{
"name": "Product gallery",
"class": "product-gallery-section",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading"
}
]
}
{% endschema %}

Get combined post from different categories using OctoberCMS RainLab Blog

I'm using Rain Lab Post in October CMS.
I have no problem using the blogPost component and get post from a single category. E.g. This is a partial where show the last 5 post from a category
[blogPosts]
pageNumber = "{{ :page }}"
categoryFilter = "{{ slug }}"
postsPerPage = 5
noPostsMessage = "No news in this category"
sortOrder = "published_at desc"
categoryPage = 404
postPage = "singlePost"
==
<li> {{category.name}}
<ul class="drop-down full-width col-5 hover-expand">
<li class="validation">
<h2 class="mm-title">{{category.name}}</h2>
</li>
{% for post in posts %}
<li>
{% for image in post.featured_images|slice(0,1) %}
<img src="{{ image.path }}" alt="">
{% endfor %}
<h3>{{post.title}}</h3>
</li>
{% endfor %}
</ul>
</li>
But now, I'm working in the home page and want to display the last post from all categories. 1 per category, 5 categories, combined.
Somebody knows how to do this?
Thanks in advance
Thanks to first aproach from Ahmed Essam, I resolve this in the next way:
function onStart(){
$categories = Db::table('rainlab_blog_categories')->get();
foreach ($categories as $key=>$category){
if($category->slug != 'uncategorized'){
$first = \Rainlab\Blog\Models\Category::with('posts')->where('id',$category->id)->first();
$data[] = $first->posts[0];
}
}
$this['posts'] = $data;
}
In the template I can use in this way
{% for post in posts %}
<li class="col-md-6 col-sm-6">
<div id="container">
{% for image in post.featured_images | slice(0,1) %}
<div class="thumb"><img src="{{image.path}}" alt=""></div>
{% endfor %}
<div class="content">
{% for category in post.categories %}
<div class="cat">{{category.name}}</div>
{% endfor %}
<h3>{{ post.title )}}</h3>
</div>
</div>
</li>
{% endfor %}
This way you have to fetch data manually in the code section of your page/layout.
function onStart() {
$first_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 1)->first();
$second_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 2)->first();
$third_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 3)->first();
$fourth_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 4)->first();
$fifth_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 5)->first();
$array = $first_category_post->merge($second_category_post);
$array = $array->merge($third_category_post);
$array = $array->merge($fourth_category_post);
$array = $array->merge($fifth_category_post);
$this['posts'] = $array;
}
Another solution is to use usual php array_merge.
function onStart() {
$first_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 1)->first();
$second_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 2)->first();
$third_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 3)->first();
$fourth_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 4)->first();
$fifth_category_post = \Rainlab\Blog\Models\Post::orderBy('id', 'desc')->where('category_id', 5)->first();
$array = array_merge($first_category_post->toArray(), $second_category_posts->toArray());
$array = array_merge($array, $third_category_post->toArray());
$array = array_merge($array, $fourth_category_post->toArray());
$array = array_merge($array, $fifth_category_post->toArray());
$this['posts'] = $array->toJson();
}

Resources