@@ -99,6 +99,18 @@ class Meta(Document): | |||||
def get_options(self, fieldname): | def get_options(self, fieldname): | ||||
return self.get_field(fieldname).options | return self.get_field(fieldname).options | ||||
def get_search_fields(self): | |||||
search_fields = self.search_fields or "name" | |||||
search_fields = [d.strip() for d in search_fields.split(",")] | |||||
if "name" not in search_fields: | |||||
search_fields.append("name") | |||||
return search_fields | |||||
def get_list_fields(self): | |||||
return ["name"] + [d.fieldname \ | |||||
for d in self.fields if (d.in_list_view and d.fieldtype in type_map)] | |||||
def process(self): | def process(self): | ||||
# don't process for special doctypes | # don't process for special doctypes | ||||
# prevent's circular dependency | # prevent's circular dependency | ||||
@@ -203,7 +215,7 @@ doctype_table_fields = [ | |||||
def is_single(doctype): | def is_single(doctype): | ||||
try: | try: | ||||
return frappe.db.get_value("DocType", doctype, "issingle") | return frappe.db.get_value("DocType", doctype, "issingle") | ||||
except IndexError, e: | |||||
except IndexError: | |||||
raise Exception, 'Cannot determine whether %s is single' % doctype | raise Exception, 'Cannot determine whether %s is single' % doctype | ||||
def get_parent_dt(dt): | def get_parent_dt(dt): | ||||
@@ -0,0 +1,84 @@ | |||||
{% block title %}{{ type }} {{ _("List") }}{% endblock %} | |||||
{% block header %} | |||||
<h2>{{ type }} {{ _("List") }}</h2> | |||||
{% endblock %} | |||||
{% block content %} | |||||
<!-- no-sidebar --> | |||||
<div class="row"> | |||||
<div class=" col-sm-offset-8 col-sm-4"> | |||||
<form class="form-inline form-search" action="/list"> | |||||
<div class="input-group"> | |||||
<input class="form-control" type="text" name="txt" | |||||
placeholder="Search..." value="{{ txt or '' }}"> | |||||
<input type="hidden" name="type" value="{{ type }}"> | |||||
<span class="input-group-btn"> | |||||
<button class="btn btn-default" type="submit"> | |||||
<i class="icon-search"></i></button> | |||||
</span> | |||||
</div> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
<br> | |||||
{% if txt %} | |||||
<div class="alert alert-warning">Results filtered by <b>{{ txt }}</b>. <a href="/list?type={{ type }}" class="close">×</a></div> | |||||
{% endif %} | |||||
<div class="list-group" data-type="{{ type }}" data-txt="{{ txt or '[notxt]' }}"> | |||||
{% for item in items %} | |||||
<div class="list-group-item"> | |||||
{{ item }} | |||||
</div> | |||||
{% endfor %} | |||||
</div> | |||||
<div class="more-block text-center hide"> | |||||
<button class="btn btn-default btn-more">More</button> | |||||
</div> | |||||
{% endblock %} | |||||
{% block script %} | |||||
<script> | |||||
frappe.ready(function() { | |||||
// show more button if len is 20 | |||||
$list_group = $(".list-group[data-type='{{ type }}'][data-txt='{{ txt or "[notxt]" }}']"); | |||||
// more ajax | |||||
frappe.start = 20; | |||||
$(".btn-more").on("click", function() { | |||||
$.ajax({ | |||||
url:"/api/method/frappe.templates.pages.list.get_items", | |||||
data: { | |||||
type: "{{ type }}", | |||||
txt: "{{ txt or '' }}", | |||||
limit_start: frappe.start | |||||
}, | |||||
statusCode: { | |||||
200: function(data) { | |||||
frappe.start += 20; | |||||
$.each(data.message.items, function(i, d) { | |||||
$('<div class="list-group-item">') | |||||
.html(d) | |||||
.appendTo($list_group); | |||||
}); | |||||
show_more(); | |||||
} | |||||
} | |||||
}) | |||||
}) | |||||
var show_more = function() { | |||||
var $items = $list_group.find(".list-group-item") | |||||
if($items.length && ($items.length % 20 === 0)) { | |||||
if($(".more-block").hasClass("hide")) | |||||
$(".more-block").removeClass("hide"); | |||||
} else { | |||||
if(!$(".more-block").hasClass("hide")) | |||||
$(".more-block").addClass("hide"); | |||||
} | |||||
}; | |||||
show_more(); | |||||
}) | |||||
</script> | |||||
{% endblock %} |
@@ -0,0 +1,46 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe, os | |||||
from frappe.modules import get_doc_path | |||||
from jinja2 import Environment, Template, FileSystemLoader | |||||
no_cache = 1 | |||||
no_sitemap = 1 | |||||
def get_context(context): | |||||
context.type = frappe.local.form_dict.type | |||||
context.txt = frappe.local.form_dict.txt | |||||
context.update(get_items(context.type, context.txt)) | |||||
return context | |||||
@frappe.whitelist(allow_guest=True) | |||||
def get_items(type, txt, limit_start=0): | |||||
meta = frappe.get_meta(type) | |||||
filters, or_filters = [], [] | |||||
out = frappe._dict() | |||||
if txt: | |||||
if meta.search_fields: | |||||
for f in meta.get_search_fields(): | |||||
or_filters.append([type, f.strip(), "like", "%" + txt + "%"]) | |||||
else: | |||||
filters.append([type, "name", "like", "%" + txt + "%"]) | |||||
out.raw_items = frappe.get_list(type, fields = meta.get_list_fields(), | |||||
filters=filters, or_filters = or_filters, limit_start=limit_start, | |||||
limit_page_length = 20) | |||||
template_path = os.path.join(get_doc_path(meta.module, "DocType", meta.name), "list_item.html") | |||||
if os.path.exists(template_path): | |||||
env = Environment(loader = FileSystemLoader(".")) | |||||
template = env.get_template(template_path) | |||||
else: | |||||
template = Template("""<div><a href="/{{ doctype }}/{{ item.name }}"> | |||||
{{ item.name }}</a></div>""") | |||||
out.items = [template.render(item=i, doctype=type) for i in out.raw_items] | |||||
return out |
@@ -7,4 +7,4 @@ | |||||
<div class="message-content"> | <div class="message-content"> | ||||
<p>{{ message }}</p> | <p>{{ message }}</p> | ||||
</div> | </div> | ||||
{% endblock %} | |||||
{% endblock %} |
@@ -0,0 +1,40 @@ | |||||
{% block title %}{{ doc.doctype }} / {{ doc.name }}{% endblock %} | |||||
{% block header %} | |||||
<h2>{{ doc.name }}</h2> | |||||
<p>{{ doc.doctype }} | |||||
{% endblock %} | |||||
{% block content %} | |||||
{% if custom_view %} | |||||
{{ custom_view }} | |||||
{% else %} | |||||
{% for df in meta.fields %} | |||||
{% if not df.hidden and not df.permlevel and not df.print_hide %} | |||||
{% if df.fieldtype=="Section Break" %} | |||||
<h2>{{ df.label or "" }}</h2> | |||||
{% elif df.fieldtype=="Column Break" %} | |||||
{% elif df.fieldtype=="Table" %} | |||||
{% else %} | |||||
<div class="row"> | |||||
<div class="col-sm-4 text-right"> | |||||
{% if df.fieldtype not in ("Image",) %} | |||||
<label>{{ df.label }}</label> | |||||
{% endif %} | |||||
</div> | |||||
<div class="col-sm-8"> | |||||
{% if df.fieldtype=="Check" %} | |||||
<i class="{{ 'icon-check' if doc[df.fieldname] else 'icon-check-empty' }}"></i> | |||||
{% elif df.fieldtype=="Image" %} | |||||
<img src="{{ doc[meta.get_field(df.fieldname).options] }}" class="img-responsive"> | |||||
{% else %} | |||||
{{ doc[df.fieldname] }} | |||||
{% endif %} | |||||
</div> | |||||
</div> | |||||
{% endif %} | |||||
{% endif %} | |||||
{% endfor %} | |||||
{% endif %} | |||||
{% endblock %} |
@@ -0,0 +1,14 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
no_cache = 1 | |||||
no_sitemap = 1 | |||||
def get_context(context): | |||||
return { | |||||
"doc": frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.name), | |||||
"meta": frappe.get_meta(frappe.local.form_dict.doctype) | |||||
} |
@@ -221,6 +221,9 @@ $.extend(frappe, { | |||||
if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) | if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) | ||||
return; | return; | ||||
if (link.getAttribute("target")) | |||||
return; | |||||
// Ignore cross origin links | // Ignore cross origin links | ||||
if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) | if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) | ||||
return; | return; | ||||
@@ -8,7 +8,7 @@ from werkzeug.wrappers import Response | |||||
from frappe.website.context import get_context | from frappe.website.context import get_context | ||||
from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache | from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache | ||||
from frappe.website.permissions import get_access, clear_permissions | |||||
from frappe.website.permissions import clear_permissions | |||||
class PageNotFoundError(Exception): pass | class PageNotFoundError(Exception): pass | ||||
@@ -18,9 +18,25 @@ def render(path, http_status_code=None): | |||||
try: | try: | ||||
data = render_page(path) | data = render_page(path) | ||||
except frappe.DoesNotExistError, e: | except frappe.DoesNotExistError, e: | ||||
path = "404" | |||||
doctype, name = get_doctype_from_path(path) | |||||
if doctype and name: | |||||
path = "view" | |||||
frappe.local.form_dict.doctype = doctype | |||||
frappe.local.form_dict.name = name | |||||
elif doctype: | |||||
path = "list" | |||||
frappe.local.form_dict.type = doctype | |||||
else: | |||||
path = "404" | |||||
http_status_code = e.http_status_code | |||||
data = render_page(path) | |||||
except frappe.PermissionError, e: | |||||
path = "message" | |||||
frappe.local.message = "Did you log out?" | |||||
frappe.local.message_title = "Not Permitted" | |||||
data = render_page(path) | data = render_page(path) | ||||
http_status_code = e.http_status_code | http_status_code = e.http_status_code | ||||
@@ -31,6 +47,24 @@ def render(path, http_status_code=None): | |||||
return build_response(path, data, http_status_code or 200) | return build_response(path, data, http_status_code or 200) | ||||
def get_doctype_from_path(path): | |||||
doctypes = [d[0] for d in frappe.get_list("DocType", as_list=True)] | |||||
parts = path.split("/") | |||||
doctype = parts[0] | |||||
name = parts[1] if len(parts) > 1 else None | |||||
if doctype in doctypes: | |||||
return doctype, name | |||||
# try scrubbed | |||||
doctype = doctype.replace("_", " ").title() | |||||
if doctype in doctypes: | |||||
return doctype, name | |||||
return None, None | |||||
def build_response(path, data, http_status_code): | def build_response(path, data, http_status_code): | ||||
# build response | # build response | ||||
response = Response() | response = Response() | ||||
@@ -61,7 +61,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, | |||||
# build from doctype | # build from doctype | ||||
if txt: | if txt: | ||||
if meta.search_fields: | if meta.search_fields: | ||||
for f in meta.search_fields.split(","): | |||||
for f in meta.get_search_fields(): | |||||
or_filters.append([doctype, f.strip(), "like", "%" + txt + "%"]) | or_filters.append([doctype, f.strip(), "like", "%" + txt + "%"]) | ||||
else: | else: | ||||
filters.append([doctype, searchfield or "name", "like", | filters.append([doctype, searchfield or "name", "like", | ||||