I have rigged up a custom "Advanced Search" view by slightly extending the flask admin index view. This takes the user to a custom form rendered using the flask admin form rendering machinery to do most of the work.
The form is defined as follows:
class PaperSearchForm(FlaskForm):
return_url = HiddenField()
title = StringField()
abstract = StringField()
start_date = DateTimeField()
end_date = DateTimeField()
doi = StringField()
pubmed_id = StringField()
link = StringField()
journals = QuerySelectMultipleField(
query_factory=_get_model(Journal),
)
authors = QuerySelectMultipleField(
query_factory=_get_model(Author),
)
keywords = QuerySelectMultipleField(
query_factory=_get_model(Keyword),
)
chapters = QuerySelectMultipleField(
query_factory=_get_model(Chapter),
)
printed = BooleanField(default=True)
unprinted = BooleanField(default=True)
submit = SubmitField('Search')
The Advanced Search model view is defined like this:
from flask import flash
from flask import redirect
from flask import request
from flask_admin import BaseView
from flask_admin import expose
from flask_wtf import FlaskForm
from flask_login import current_user
from .forms import PaperSearchForm
class AdvancedPaperSearchView(BaseView):
form_base_class = FlaskForm
def __init__(self,
name=None,
category=None,
endpoint=None,
url=None,
template='auth/model/paper/advanced_search.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None
):
super(AdvancedPaperSearchView, self).__init__(
name,
category,
endpoint,
url or '/',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
def is_visible(self):
return False
def is_accessible(self):
if current_user.is_authenticated:
return current_user.can_view_papers()
return False
#expose('/', methods=['GET', 'POST'])
def index(self):
form = PaperSearchForm()
form.return_url.data = request.args['return_url']
self._template_args['form'] = form
self._template_args['cancel_url'] = request.args['return_url']
return self.render(self._template)
#expose('/search', methods=['POST'])
def search(self):
# List view generates list of models based on 'term'= from request.args.get('term', default=None)
# Manually setting these arguments will serve as the advanced search functionality
form = PaperSearchForm() # ???
search = None # ???
filter = None # ???
flash('How to apply multiple filters?', 'error')
return redirect('papermodelview.index', search=search, filter=filter) # ???
Then, the template is defined like this:
{% extends "admin/master.html" %}
{% import 'admin/lib.html' as lib with context %}
{% from 'admin/lib.html' import extra with context %} {# backward compatible #}
{% from 'admin/lib.html' import render_field with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
{% block navlinks %}
<ul class="nav nav-tabs">
<li>
List
</li>
<li class="active">
Advanced Search
</li>
</ul>
{% endblock %}
<form method="post" action="{{ url_for('advancedpapersearchview.search') }}">
{{ form.return_url }}
{{ form.csrf_token }}
{{ render_field(form, form.title) }}
{{ render_field(form, form.abstract) }}
{{ render_field(form, form.start_date) }}
{{ render_field(form, form.end_date) }}
{{ render_field(form, form.doi) }}
{{ render_field(form, form.pubmed_id) }}
{{ render_field(form, form.link) }}
{{ render_field(form, form.journals) }}
{{ render_field(form, form.authors) }}
{{ render_field(form, form.chapters) }}
{{ render_field(form, form.keywords) }}
{{ render_field(form, form.printed) }}
{{ render_field(form, form.unprinted) }}
<div class="row">
<div class="col-xs-12">
{{ form.submit(class="btn") }}
</div>
</div>
<div class="row">
<div class="col-xs-12">
<a href="{{ cancel_url }}" class="btn warning">
Cancel
</a>
</div>
</div>
</form>
{% endblock %}
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
<script src="/static/vendor/jquery.min.js" type="text/javascript">/script>
{# use /static/bootstrap2/js/bootstrap.min.js if you are using bootstrap2 #}
<script src="/static/bootstrap3/js/bootstrap.min.js" type="text/javascript"></script>
<script src="/static/vendor/moment.min.js" type="text/javascript"></script>
<script src="/static/vendor/select2/select2.min.js" type="text/javascript"></script>
{% endblock %}
In the Paper Model View, the filters are defined like this:
class PaperModelView(MainModelView):
# ...
column_filters = [
'chapter_paper_assoc.printed',
'journal_paper_assoc.publication_date',
'chapters.name',
'chapters.number',
'journals.name',
'authors.last_name',
'keywords.keyword',
]
So, I commented a bunch of ??? where I don't know what to do. How do I map the fields of my form (specific attributes of the models selected) to the filters that are defined by 'column_filters' in the model view.
I.e -- rather than overriding the index view search handling to actually perform a search, I could instead apply a bunch of filters by passing this information to the index view, which retrieves this information with:
filters=response.args.get('filter', None)
Is there a better approach?
Thanks
Alright, things are about to get ugly, so hold onto your hat.
This is what I ended up writing to accomplish this functionality, and it is code that I am not particularly proud to have written. It works, but that said, please suggest a cleaner way to do this if you feel like it.
Here is the advanced search view:
from flask import flash
from flask import redirect
from flask import request
from flask import url_for
from flask_admin import BaseView
from flask_admin import expose
from flask_wtf import FlaskForm
from flask_login import current_user
from app import admin
from .forms import PaperSearchForm
class AdvancedPaperSearchView(BaseView):
form_base_class = FlaskForm
def __init__(self,
name=None,
category=None,
endpoint=None,
url=None,
template='auth/model/paper/advanced_search.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None
):
super(AdvancedPaperSearchView, self).__init__(
name,
category,
endpoint,
url or '/',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
def is_visible(self):
return False
def is_accessible(self):
if current_user.is_authenticated:
return current_user.can_view_papers()
return False
#expose('/', methods=['GET', 'POST'])
def index(self):
form = PaperSearchForm()
form.return_url.data = request.args['return_url']
self._template_args['form'] = form
self._template_args['cancel_url'] = request.args['return_url']
return self.render(self._template)
#expose('/search', methods=['POST'])
def search(self):
form = PaperSearchForm()
# The goal here is to get the paper model view from the currently running app (and its admin extension). Once
# the model view is here, use it to get the available filters (get their keys and operations). Use the existing
# request args and add filters to them using the key and operations defined in the model view.
paper_model_view = None
for view in admin._views:
# There must be a better way to do this, and I know this is a WTF, but I don't have the vocabulary to search
# the flask admin documentation for the right way to get the instance of the model view from the admin
# object. I need the *instance*, with the filters created and added to that instance by the init... so...
# not clean or pretty ... and permanently restricts name of paper model view ... TODO: Fix? Rewrite?
# - Chris, March 2017
if "PaperModelView object" in view.__repr__():
paper_model_view = view
# ._filters contains the list of all filters
# ._filter_args contains a dictionary of keys and filter objects for url construction
# each filter is persisted with request.args, the query string is <flt[position]_[key]=[value]> or
# <flt[position]_[key]> for filters without values
# The filter is accessed by looking up the filter object with the key value, and then the filters are listed
# in the order of the position provided in the query string. I am unsure whether or not they are applied in
# this order, but that seems like what is happening.
filters = {}
i = 0
str = "flt{pos}_{key}"
def __check(column, table, filter):
return (column in filter.column.name and table in filter.column.table.name.__repr__())
# Sorry for this...
# Iterate through each filter available for the view. Check if it's name and operation are something that
# will enact a portion of the search, then add it's filter (in the format expected) to a dictionary. The index
# variable i keeps track of the "count" of filters that have been added and uses this as the position of the
# filter.
for key, key_filter in paper_model_view._filter_args.items():
filter = key_filter[1]
if hasattr(filter, 'column'):
if __check("title", "papers", filter):
if "FilterLike" in filter.operation.__repr__():
if form.title.data:
filters[str.format(pos=i, key=key)] = form.title.data
i += 1
if __check("abstract", "papers", filter):
if "FilterLike" in filter.operation.__repr__():
if form.abstract.data:
filters[str.format(pos=i, key=key)] = form.abstract.data
i += 1
if __check("publication_date", "journal_paper", filter):
if "DateSmaller" in filter.operation.__repr__():
if form.end_date.data:
filters[str.format(pos=i, key=key)] = form.end_date.data.date() # Only keeps the date for the filter
i += 1
elif "DateGreater" in filter.operation.__repr__():
if form.start_date.data:
filters[str.format(pos=i, key=key)] = form.start_date.data.date()
i += 1
if __check("doi", "papers", filter):
if "FilterLike" in filter.operation.__repr__():
if form.doi.data:
filters[str.format(pos=i, key=key)] = form.doi.data
i += 1
if __check("pubmed_id", "papers", filter):
if "FilterLike" in filter.operation.__repr__():
if form.pubmed_id.data:
filters[str.format(pos=i, key=key)] = form.pubmed_id.data
i += 1
if __check("link", "papers", filter):
if "FilterLike" in filter.operation.__repr__():
if form.link.data:
filters[str.format(pos=i, key=key)] = form.link.data
i += 1
if __check("name", "journal", filter):
if "FilterLike" in filter.operation.__repr__():
if form.journals.data:
for journal in form.journals.data:
filters[str.format(pos=i, key=key)] = journal.name
i += 1
if __check("first_name", "authors", filter):
if "FilterLike" in filter.operation.__repr__():
for author in form.authors.data:
filters[str.format(pos=i, key=key)] = author.first_name
i += 1
if __check("last_name", "authors", filter):
if "FilterLike" in filter.operation.__repr__():
for author in form.authors.data:
filters[str.format(pos=i, key=key)] = author.last_name
i += 1
if __check("keyword", "keywords", filter):
if "FilterLike" in filter.operation.__repr__():
for keyword in form.keywords.data:
filters[str.format(pos=i, key=key)] = keyword.keyword
i += 1
if __check("name", "chapters", filter):
if "FilterLike" in filter.operation.__repr__():
for chapter in form.chapters.data:
filters[str.format(pos=i, key=key)] = chapter.name
i += 1
if __check("printed", "chapter_paper", filter):
if "FilterEqual" in filter.operation.__repr__():
if form.printed.data == True:
if form.unprinted.data == False: # Printed only
filters[str.format(pos=i, key=key)] = 1 # True
i += 1
else:
pass # Both are True
else:
if form.unprinted.data == True: # Unprinted only
filters[str.format(pos=i, key=key)] = 0 # False
i += 1
else:
pass # Both are False
else:
continue
flash('Filters successfully applied', 'success')
return redirect(url_for('paper.index_view', **filters))
I have this typoscript to request content only (without the header, css, js, etc)
ajaxCall = PAGE
ajaxCall {
typeNum = 999
config.disableAllHeaderCode = 1
config.disablePrefixComment = true
# config.additionalHeaders = Content-type: text/html; charset=utf-8
config.metaCharset = UTF-8
10 = COA
10 < styles.content.get
10.stdWrap.prepend > # supress feEditAdvanced-firstWrapper - Bug in typo3 4.3.1
}
This works fine as long as I am in the default language. But when I want to use this pageType on another language &L=1 it does not work and I get nothing.
I tested around a bit and found out that the problem is here
10 = COA
10 < styles.content.get
It looks like when there is a language parameter the styles.content.get is empty.
Edit: I try to get the content with my own CONTENT object
ajaxCallw = PAGE
ajaxCallw {
typeNum = 1000
config.disableAllHeaderCode = 1
config.disablePrefixComment = true
# config.additionalHeaders = Content-type: text/html; charset=utf-8
config.metaCharset = UTF-8
10 = CONTENT
10 {
table = tt_content
select.orderBy = sorting
select.where = colPos=0
select.languageField = 4
}
}
It looks like select.languageField = 4 gets completly ignored, as it still displays me the content from the default langauge. And when I call a URL with &type=1000&L=4 it still does not show me anything. (Without the L=4 parameter it shows me the default language content)
select.languageField is a pointer to the field in the database
select.languageField = sys_language_uid
As far as I remember the language variable is not parsed by default, so you have to set it yourself
ajaxCall {
# your code
config.sys_language_uid = 0
}
# condition for the language. Adjust it to your language id
[globalVar = GP:L = 1]
ajaxCall.config.sys_language_uid = 1
[global]
The sys_language_overlay = hideNonTranslated was the problem.
Adding: ajaxCall.config.sys_language_overlay = 0 solved the problem.
ajaxCall = PAGE
ajaxCall {
typeNum = 999
config.disableAllHeaderCode = 1
config.disablePrefixComment = true
# config.additionalHeaders = Content-type: text/html; charset=utf-8
config.metaCharset = UTF-8
config.sys_language_overlay = 0
10 = COA
10 < styles.content.get
10.stdWrap.prepend > # supress feEditAdvanced-firstWrapper - Bug in typo3 4.3.1
}
I am not sure why exactly this works now..
styles.content.get is just a CONTENT object that gets the content of col0 (rendered true or with css_styled_content) you can better just fetch your content with your own CONTENT object, thus you can have more control of it.
Today I have done one typo3 upgrade from 4.5 to 6.2. Initially from 4.5 to 4.7 everything was working fine with out any issues. After upgrading TYPO3 6.2 content elements like textimage and image only are not rendering . Can any one please tell me why this is happening? The below given typoscript is used to rendering banner images in the website. Can any one give me a hint?
#####################
### lib.keyVisual #
#####################
lib.keyVisual = COA
lib.keyVisual {
5 = LOAD_REGISTER
5.maxImageWidth = 960
5.maxImageWidthInText = 960
50 = RECORDS
50.source.current = 1
50.tables = tt_content
# remove all divs from textpic rendering
50.conf.tt_content.stdWrap.innerWrap.cObject.default.15.value = item
50.conf.tt_content.textpic.20.layout >
50.conf.tt_content.textpic.20.layout = TEXT
50.conf.tt_content.textpic.20.layout.value = ###IMAGES######TEXT###
50.conf.tt_content.textpic.20.imageStdWrap.dataWrap =
50.conf.tt_content.textpic.20.imageStdWrapNoWidth.dataWrap =
50.conf.tt_content.textpic.20.imageColumnStdWrap.dataWrap =
50.conf.tt_content.textpic.20.rendering.simple.imageStdWrapNoWidth.dataWrap =
50.conf.tt_content.textpic.20.rendering.simple.imageStdWrap.dataWrap =
50.conf.tt_content.textpic.20.rendering.simple.imageStdWrapNoWidth.wrap =
50.conf.tt_content.textpic.20.rendering.dl.imageStdWrapNoWidth.dataWrap =
50.conf.tt_content.textpic.20.rendering.dl.imageStdWrap.dataWrap =
50.conf.tt_content.textpic.20.rendering.dl.imageStdWrapNoWidth.wrap =
50.conf.tt_content.textpic.20.text.wrap.cObject = CASE
50.conf.tt_content.textpic.20.text.wrap.cObject {
key.field = imageorient
key.stdWrap.wrap = |+1
key.prioriCalc = 1
1 = TEXT
1.value = <div class="item-position1"> | </div>
2 = TEXT
2.value = <div class="item-position2"> | </div>
9 = TEXT
9.value = <div class="item-position3"> | </div>
4 = TEXT
4.value = <div class="item-position4"> | </div>
default = TEXT
default.value = <div class="item-position-default item-position-{field:imageorient}"> | </div>
default.insertData = 1
}
100 = RESTORE_REGISTER
wrap.required = 1
wrap (
<div id="page-hero" class="page-hero">
<div class="slides_container">
|
</div>
<a class="prev" href="#"></a>
<a class="next" href="#"></a>
</div>
)
}
BR
Siva
It's probably the function IMAGE($conf) that fails to render it. You can debug it in typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
If it's not that exact one it might be IMGTEXT or a similar one.
After an upgrade from TYPO3 4.x to 6.2 you must perform several steps to get the images back.
Install Tool - Configuration presets: Image handling settings -> Use the detected (marked green) Image Magick or Graphics Magick
Install Tool - Test Setup and make some of the image tests
Install Tool - Clean Up: cache_imagesizes
Install Tool - Clean Up: Clear processed files
TYPO3 Backend - File Module: Click on the Filelist. This will load all images found there into the File Abstraction Layer (FAL)
I have a plugin for a custom content element. The plugin contains a flexform. I'd like to use a value from the flexform in my TypoScript setup. How can I do this?
More specifically, the flexform is defined as:
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<TCEforms>
<sheetTitle>LLL:EXT:cb_foundation/Resources/Private/Language/locallang_db.xlf:magellan.sheetTitle</sheetTitle>
</TCEforms>
<type>array</type>
<el>
<settings.magellan.cols>
<TCEforms>
<label>LLL:EXT:cb_foundation/Resources/Private/Language/locallang_db.xlf:magellan.cols</label>
<config>
<type>input</type>
<size>20</size>
<eval>trim</eval>
</config>
</TCEforms>
</settings.magellan.cols>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
My custom element is added to tt_content using the following TypoScript:
lib.cb_foundation.magellan = HMENU
lib.cb_foundation.magellan {
1 = TMENU
1 {
sectionIndex = 1
sectionIndex.type = header
sectionIndex.useColPos = 0
wrap = <div data-magellan-expedition="fixed"><dl class="sub-nav"> | </dl></div>
NO {
allWrap = <dd data-magellan-arrival="c{field:sectionIndex_uid}">|</dd>
allWrap.insertData = 1
}
}
special = list
special.value.data = page:uid
}
tt_content.cbfoundation_magellan =< lib.cb_foundation.magellan
What I want to do is to set tt_content.cbfoundation_magellan.1.sectionIndex.useColPos using the value found in the flexform. How can I do that?
This is not possible. Typoscript does not parse values from a flexform, unfortunately.
You need an extbase controller or pi1 to get the values from the flexform and assign them to a separate template which renders your custom content element.
Extbase Example:
class ContentElementsController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
public function YourContentElementAction() {
// Get the data object (contains the tt_content fields)
$data = $this->configurationManager->getContentObject()->data;
// Append flexform values
$this->configurationManager->getContentObject()->readFlexformIntoConf($data['pi_flexform'], $data);
// Assign to template
$this->view->assign('data', $data);
}
}
I dont know, if the extension gridelements or TYPO3 6.2.26 bring the functionality, but the following works for me:
dataWrap = {field:flexform_YOURFIELD}
The following StackOverflow answer describes, how plugin flexform settings of EXT News can be accessed from TS Setup using a small PHP USER Class:
https://stackoverflow.com/a/40006158/430742
It should be easy to adapt for other plugins and their flexform settings.
As stated here there is a solution now in TYPO3 >= 8.4.
The feature is documented in the TYPO3 Core Changelog.
Hi I like to add some additional menu items to a defined mainmenu.
Here is my attempt:
[PIDinRootline = 18]
lib.mainmenu.1 {
ACT = 1
ACT{
after.cObject = HMENU
after.cObject < lib.catmenu
}
}
[END]
well working so far, but the menu items are only showing up when the page 18 is active. I'd like them to be allways visible.
Any Idea?
further information:
my menus
lib.catmenu = HMENU
lib.catmenu{
1 = TMENU
1 {
wrap = <div class="megamenu-dropdown"><ul class="level0"> | </ul></div>
expAll = 1
noBlur = 1
NO = 1
NO.wrapItemAndSub = <li class="level1 nav-1-1 default"> | </li>
ACT = 1
ACT.wrapItemAndSub = <li class="level1 nav-1-1 active"> | </li>
}
}
lib.mainmenu = HMENU
lib.mainmenu{
entryLevel = 0
1 = TMENU
1{
expAll = 1
wrap = <ul id="nav"> | </ul>
noBlur = 1
NO = 1
NO {
wrapItemAndSub = <li class="level0 nav-1 level-top default"> | </li>
stdWrap.htmlSpecialChars = 1
ATagTitle.field = title
}
IFSUB = 1
IFSUB{
wrapItemAndSub = <li class="level0 nav-1 level-top default parent"> | </li>
stdWrap.htmlSpecialChars = 1
ATagTitle.field = title
}
ACT = 1
ACT{
wrapItemAndSub = <li class="level0 nav-1 level-top default active"> | </li>
}
}
edit:
Thanks for the responses.
I try to make my question more understandable.
This is how my menu usually looks like
- home
- shop
- news
- test
- subtest
This is how my menu looks like when I open the page with id 18
- home
- shop
- cat1
- cat2
- news
- test
- subtest
This is how I want my menu allways looks like
- home
- shop
- cat1
- cat2
- news
- test
- subtest
When I remove the condition [PIDinRoorline = 18] then typo3 has no Idea where to add the additional menu Items. The result is: (I've also changed ACT to NO)
- home
- cat1
- cat2
- shop
- cat1
- cat2
- news
- cat1
- cat2
- test
- subtest
So is there any way to say typoscript. "Put my additional menu Items to a specific place"
Thanks for your help!
Cheers
This is a condition that makes sure that the configuration inside is only taken into account on page 18. Just remove this condition and the configuration will apply.
[PIDinRootline = 18]
...
[END]