Exemple #1
0
    def _resolve_context(context, id):
        """
            Resolve a context filter

            @param context: the context (as a string)
            @param id: the record_id
        """

        if context == "location":
            # Show records linked to this Location & all it's Child Locations
            s = "(location)$path"
            # This version doesn't serialize_url
            #m = ("%(id)s/*,*/%(id)s/*" % dict(id=id)).split(",")
            #filter = (S3FieldSelector(s).like(m)) | (S3FieldSelector(s) == id)
            m = ("%(id)s,%(id)s/*,*/%(id)s/*,*/%(id)s" %
                 dict(id=id)).split(",")
            m = [f.replace("*", "%") for f in m]
            filter = S3FieldSelector(s).like(m)
        # @ToDo:
        #elif context == "organisation":
        #    # Show records linked to this Organisation and all it's Branches
        #    s = "(%s)" % context
        #    filter = S3FieldSelector(s) == id
        else:
            # Normal: show just records linked directly to this master resource
            s = "(%s)" % context
            filter = S3FieldSelector(s) == id

        return filter
Exemple #2
0
    def _resolve_context(r, tablename, context):
        """
            Resolve a context filter

            @param context: the context (as a string)
            @param id: the record_id
        """

        record_id = r.id
        if not record_id:
            return None

        s3db = current.s3db

        if not context:
            query = None

        elif type(context) is tuple:
            context, field = context
            query = S3FieldSelector(context) == r.record[field]

        elif context == "location":
            # Show records linked to this Location & all it's Child Locations
            s = "(location)$path"
            # This version doesn't serialize_url
            #m = ("%(id)s/*,*/%(id)s/*" % dict(id=id)).split(",")
            #filter = (S3FieldSelector(s).like(m)) | (S3FieldSelector(s) == id)
            m = ("%(id)s,%(id)s/*,*/%(id)s/*,*/%(id)s" % dict(id=record_id)).split(",")
            m = [f.replace("*", "%") for f in m]
            query = S3FieldSelector(s).like(m)
        # @ToDo:
        #elif context == "organisation":
        #    # Show records linked to this Organisation and all it's Branches
        #    s = "(%s)" % context
        #    query = S3FieldSelector(s) == id
        else:
            # Normal: show just records linked directly to this master resource
            s = "(%s)" % context
            query = S3FieldSelector(s) == record_id

        # Define target resource
        resource = s3db.resource(tablename, filter=query)
        return resource, query
Exemple #3
0
    def duplicates(self, r, **attr):
        """
            Renders a list of all currently duplicate-bookmarked
            records in this resource, with option to select two
            and initiate the merge process from here

            @param r: the S3Request
            @param attr: the controller attributes for the request
        """

        s3 = current.session.s3

        resource = self.resource
        tablename = self.tablename

        if r.http == "POST":
            return self.merge(r, **attr)

        # Bookmarks
        record_ids = []
        DEDUPLICATE = self.DEDUPLICATE
        if DEDUPLICATE in s3:
            bookmarks = s3[DEDUPLICATE]
            if tablename in bookmarks:
                record_ids = bookmarks[tablename]
        query = S3FieldSelector(resource._id.name).belongs(record_ids)
        resource.add_filter(query)

        # Representation
        representation = r.representation

        # List fields
        list_fields = resource.list_fields()

        # Start/Limit
        vars = r.get_vars
        if representation == "aadata":
            start = vars.get("iDisplayStart", None)
            limit = vars.get("iDisplayLength", None)
            sEcho = int(vars.sEcho or 0)
        else:  # catch all
            start = 0
            limit = current.manager.ROWSPERPAGE
        if limit is not None:
            try:
                start = int(start)
                limit = int(limit)
            except ValueError:
                start = None
                limit = None  # use default
        else:
            start = None  # use default
        if current.response.s3.dataTable_iDisplayLength:
            display_length = current.response.s3.dataTable_iDisplayLength
        else:
            display_length = 25
        if limit is None:
            limit = 2 * display_length

        # Datatable Filter
        totalrows = None
        if representation == "aadata":
            searchq, orderby, left = resource.datatable_filter(
                list_fields, vars)
            if searchq is not None:
                totalrows = resource.count()
                resource.add_filter(searchq)
        else:
            orderby, left = None, None

        # Get the records
        data = resource.select(list_fields,
                               start=start,
                               limit=limit,
                               orderby=orderby,
                               left=left,
                               count=True,
                               represent=True)

        displayrows = data["numrows"]
        if totalrows is None:
            totalrows = displayrows

        # Generate a datatable
        dt = S3DataTable(data["rfields"], data["rows"])

        datatable_id = "s3merge_1"
        response = current.response

        if representation == "aadata":
            output = dt.json(totalrows,
                             displayrows,
                             datatable_id,
                             sEcho,
                             dt_bulk_actions=[(current.T("Merge"), "merge",
                                               "pair-action")])

        elif representation == "html":
            # Initial HTML response
            T = current.T
            output = {"title": T("De-duplicate Records")}

            url = r.url(representation="aadata")

            #url = "/%s/%s/%s/deduplicate.aadata" % (r.application,
            #r.controller,
            #r.function)
            items = dt.html(totalrows,
                            displayrows,
                            datatable_id,
                            dt_ajax_url=url,
                            dt_displayLength=display_length,
                            dt_bulk_actions=[(T("Merge"), "merge",
                                              "pair-action")])

            output["items"] = items
            response.s3.actions = [{
                "label": str(T("View")),
                "url": r.url(target="[id]", method="read"),
                "_class": "action-btn"
            }]

            if len(record_ids) < 2:
                output["add_btn"] = DIV(
                    SPAN(T(
                        "You need to have at least 2 records in this list in order to merge them."
                    ),
                         _style="float:left; padding-right:10px;"),
                    A(T("Find more"),
                      _href=r.url(method="search",
                                  id=0,
                                  component_id=0,
                                  vars={})))
            else:
                output["add_btn"] = DIV(
                    SPAN(
                        T("Select 2 records from this list, then click 'Merge'."
                          )), )

            response.s3.dataTableID = [datatable_id]
            response.view = self._view(r, "list.html")

        else:
            r.error(501, r.ERROR.BAD_FORMAT)

        return output
Exemple #4
0
    def create_event_frame(self,
                           event_start,
                           event_end,
                           start=None,
                           end=None,
                           slots=None):
        """
            Create an event frame for the current resource

            @param event_start: the event start field (S3ResourceField)
            @param event_end: the event end field (S3ResourceField)
            @param start: the start date/time (string)
            @param end: the end date/time (string)
            @param slots: the slot length (string)

            @return: the event frame
        """

        resource = self.resource

        now = datetime.datetime.utcnow()

        dtparse = self.dtparse

        start_dt = end_dt = None

        STANDARD_SLOT = "1 day"

        # Parse start and end time
        if start:
            start_dt = dtparse(start, start=now)
        if end:
            relative_to = start_dt if start_dt else now
            end_dt = dtparse(end, start=relative_to)

        # Fall back to now if internval end is not specified
        if not end_dt:
            end_dt = now

        if not start_dt and event_start and event_start.field:
            # No interval start => fall back to first event start
            query = S3FieldSelector(event_start.selector) != None
            resource.add_filter(query)
            rows = resource.select([event_start.selector],
                                   limit=1,
                                   orderby=event_start.field,
                                   as_rows=True)
            # Remove the filter we just added
            resource.rfilter.filters.pop()
            resource.rfilter.query = None
            if rows:
                first_event = rows.first()[event_start.colname]
                if isinstance(first_event, datetime.date):
                    first_event = datetime.datetime.fromordinal(
                        first_event.toordinal())
                start_dt = first_event

        if not start_dt and event_end and event_end.field:
            # No interval start => fall back to first event end minus
            # one standard slot length:
            query = S3FieldSelector(event_end.selector) != None
            resource.add_filter(query)
            rows = resource.select([event_end.selector],
                                   limit=1,
                                   orderby=event_end.field,
                                   as_rows=True)
            # Remove the filter we just added
            resource.rfilter.filters.pop()
            resource.rfilter.query = None
            if rows:
                last_event = rows.first()[event_end.colname]
                if isinstance(last_event, datetime.date):
                    last_event = datetime.datetime.fromordinal(
                        last_event.toordinal())
                start_dt = dtparse("-%s" % STANDARD_SLOT, start=last_event)

        if not start_dt:
            # No interval start => fall back to interval end minus
            # one slot length:
            if not slots:
                slots = STANDARD_SLOT
            try:
                start_dt = dtparse("-%s" % slots, start=end_dt)
            except (SyntaxError, ValueError):
                slots = STANDARD_SLOT
                start_dt = dtparse("-%s" % slots, start=end_dt)

        if not slots:
            # No slot length =>
            # Determine optimum slot length automatically
            # @todo: determine from density of events rather than
            #        total interval length?
            seconds = abs(end_dt - start_dt).total_seconds()
            day = 86400
            if seconds < day:
                slots = "hours"
            elif seconds < 3 * day:
                slots = "6 hours"
            elif seconds < 28 * day:
                slots = "days"
            elif seconds < 90 * day:
                slots = "weeks"
            elif seconds < 730 * day:
                slots = "months"
            elif seconds < 2190 * day:
                slots = "3 months"
            else:
                slots = "years"

        return S3TimePlotEventFrame(start_dt, end_dt, slots)
Exemple #5
0
    def add_event_data(self, event_frame, resource, event_start, event_end,
                       facts):
        """
            Extract event data from resource and add them to the
            event frame

            @param event_frame: the event frame
            @param resource: the resource
            @param event_start: the event start field (S3ResourceField)
            @param event_end: the event_end field (S3ResourceField)
            @param fact: list of fact fields (S3ResourceField)

            @return: the extracted data (dict from S3Resource.select)
        """

        # Fields to extract
        fields = set(fact.selector for fact in facts)
        fields.add(event_start.selector)
        fields.add(event_end.selector)
        fields.add(resource._id.name)

        # Filter by event frame start:
        # End date of events must be after the event frame start date
        if event_end:
            end_selector = S3FieldSelector(event_end.selector)
            start = event_frame.start
            query = (end_selector == None) | (end_selector >= start)
        else:
            # No point if events have no end date
            query = None

        # Filter by event frame end:
        # Start date of events must be before event frame end date
        start_selector = S3FieldSelector(event_start.selector)
        end = event_frame.end
        q = (start_selector == None) | (start_selector <= end)
        query = query & q if query is not None else q

        # Add as temporary filter
        resource.add_filter(query)

        # Extract the records
        data = resource.select(fields)

        # Remove the filter we just added
        resource.rfilter.filters.pop()
        resource.rfilter.query = None

        # Do we need to convert dates into datetimes?
        convert_start = True if event_start.ftype == "date" else False
        convert_end = True if event_start.ftype == "date" else False
        fromordinal = datetime.datetime.fromordinal
        convert_date = lambda d: fromordinal(d.toordinal())

        # Column names for extractions
        pkey = str(resource._id)
        start_colname = event_start.colname
        end_colname = event_end.colname

        # Use table name as event type
        tablename = resource.tablename

        # Create the events
        events = []
        add_event = events.append
        for row in data["rows"]:
            values = dict((fact.colname, row[fact.colname]) for fact in facts)
            start = row[start_colname]
            if convert_start:
                start = convert_date(start)
            end = row[end_colname]
            if convert_end:
                end = convert_date(end)
            event = S3TimePlotEvent(row[pkey],
                                    start=start,
                                    end=end,
                                    values=values,
                                    event_type=tablename)
            add_event(event)

        # Extend the event frame with these events
        if events:
            event_frame.extend(events)

        return data
Exemple #6
0
    def _datalist(self, r, widget, **attr):
        """
            Generate a dataList

            @param r: the S3Request instance
            @param widget: the widget as a tuple: (label, tablename, icon, filter)
            @param attr: controller attributes for the request
        """

        T = current.T
        s3db = current.s3db
        id = r.id
        context = widget.get("context", None)
        if context:
            context = self._resolve_context(context, id)
        s3db.context = context

        tablename = widget.get("tablename", None)
        resource = s3db.resource(tablename, context=True)
        table = resource.table

        # Config Options:
        # 1st choice: Widget
        # 2nd choice: get_config
        # 3rd choice: Default
        config = resource.get_config
        list_fields = widget.get("list_fields", config("list_fields", None))
        list_layout = widget.get("list_layout", config("list_layout", None))
        orderby = widget.get(
            "orderby", config("list_orderby", ~resource.table.created_on))

        filter = widget.get("filter", None)
        if filter:
            resource.add_filter(filter)

        # Use the widget-index to create a unique ID
        listid = "profile-list-%s-%s" % (tablename, widget["index"])

        # Page size
        pagesize = 4
        representation = r.representation
        if representation == "dl":
            # Ajax-update
            get_vars = r.get_vars
            record_id = get_vars.get("record", None)
            if record_id is not None:
                # Ajax-update of a single record
                resource.add_filter(S3FieldSelector("id") == record_id)
                start, limit = 0, 1
            else:
                # Ajax-update of full page
                start = get_vars.get("start", None)
                limit = get_vars.get("limit", None)
                if limit is not None:
                    try:
                        start = int(start)
                        limit = int(limit)
                    except ValueError:
                        start, limit = 0, pagesize
                else:
                    start = None
        else:
            # Page-load
            start, limit = 0, pagesize

        # Ajax-delete items?
        if representation == "dl" and r.http in ("DELETE", "POST"):
            if "delete" in r.get_vars:
                return self._dl_ajax_delete(r, resource)
            else:
                r.error(405, r.ERROR.BAD_METHOD)

        # dataList
        datalist, numrows, ids = resource.datalist(fields=list_fields,
                                                   start=start,
                                                   limit=limit,
                                                   listid=listid,
                                                   orderby=orderby,
                                                   layout=list_layout)
        # Render the list
        ajaxurl = r.url(vars={"update": widget["index"]}, representation="dl")
        data = datalist.html(ajaxurl=ajaxurl, pagesize=pagesize)
        if numrows == 0:
            msg = P(I(_class="icon-folder-open-alt"),
                    BR(),
                    S3CRUD.crud_string(tablename, "msg_no_match"),
                    _class="empty_card-holder")
            data.insert(1, msg)

        if representation == "dl":
            # This is an Ajax-request, so we don't need the wrapper
            current.response.view = "plain.html"
            return data

        # Interactive only below here
        label = widget.get("label", "")
        if label:
            label = T(label)
        icon = widget.get("icon", "")
        if icon:
            icon = TAG[""](I(_class=icon), " ")

        # Permission to create new items?
        insert = widget.get("insert", True)
        if insert and current.auth.s3_has_permission("create", table):
            #if r.tablename = "org_organisation":
            # @ToDo: Special check for creating resources on Organisation profile
            if filter:
                vars = filter.serialize_url(filter)
            else:
                vars = Storage()
            vars.refresh = listid
            if context:
                filters = context.serialize_url(resource)
                for f in filters:
                    vars[f] = filters[f]
            default = widget.get("default", None)
            if default:
                k, v = default.split("=", 1)
                vars[k] = v
            title_create = widget.get("title_create", None)
            if title_create:
                title_create = T(title_create)
            else:
                title_create = S3CRUD.crud_string(tablename, "title_create")
            c, f = tablename.split("_", 1)
            c = widget.get("create_controller", c)
            f = widget.get("create_function", f)
            create = A(
                I(_class="icon icon-plus-sign small-add"),
                _href=URL(c=c, f=f, args=["create.popup"], vars=vars),
                _class="s3_modal",
                _title=title_create,
            )
        else:
            create = ""

        if numrows > pagesize:
            # Button to display the rest of the records in a Modal
            more = numrows - pagesize
            vars = {}
            if context:
                filters = context.serialize_url(resource)
                for f in filters:
                    vars[f] = filters[f]
            if filter:
                filters = filter.serialize_url(resource)
                for f in filters:
                    vars[f] = filters[f]
            c, f = tablename.split("_", 1)
            url = URL(c=c, f=f, args=["datalist.popup"], vars=vars)
            more = DIV(A(
                BUTTON(
                    "%s (%s)" % (T("see more"), more),
                    _class="btn btn-mini",
                    _type="button",
                ),
                _class="s3_modal",
                _href=url,
                _title=label,
            ),
                       _class="more_profile")
        else:
            more = ""

        # Render the widget
        output = DIV(create,
                     H4(icon, label, _class="profile-sub-header"),
                     DIV(data, more, _class="card-holder"),
                     _class="span6")

        return output
Exemple #7
0
    def _datalist(self, r, widget, **attr):
        """
            Generate a data list

            @param r: the S3Request instance
            @param widget: the widget definition as dict
            @param attr: controller attributes for the request
        """

        T = current.T
        s3db = current.s3db
        
        context = widget.get("context", None)
        tablename = widget.get("tablename", None)
        resource, context = self._resolve_context(r, tablename, context)

        # Config Options:
        # 1st choice: Widget
        # 2nd choice: get_config
        # 3rd choice: Default
        config = resource.get_config
        list_fields = widget.get("list_fields", 
                                 config("list_fields", None))
        list_layout = widget.get("list_layout", 
                                 config("list_layout", None))
        orderby = widget.get("orderby",
                             config("list_orderby",
                                    ~resource.table.created_on))

        filter = widget.get("filter", None)
        if filter:
            resource.add_filter(filter)

        # Use the widget-index to create a unique ID
        listid = "profile-list-%s-%s" % (tablename, widget["index"])

        # Page size
        pagesize = widget.get("pagesize", 4)
        representation = r.representation
        if representation == "dl":
            # Ajax-update
            get_vars = r.get_vars
            record_id = get_vars.get("record", None)
            if record_id is not None:
                # Ajax-update of a single record
                resource.add_filter(S3FieldSelector("id") == record_id)
                start, limit = 0, 1
            else:
                # Ajax-update of full page
                start = get_vars.get("start", None)
                limit = get_vars.get("limit", None)
                if limit is not None:
                    try:
                        start = int(start)
                        limit = int(limit)
                    except ValueError:
                        start, limit = 0, pagesize
                else:
                    start = None
        else:
            # Page-load
            start, limit = 0, pagesize

        # Ajax-delete items?
        if representation == "dl" and r.http in ("DELETE", "POST"):
            if "delete" in r.get_vars:
                return self._dl_ajax_delete(r, resource)
            else:
                r.error(405, r.ERROR.BAD_METHOD)

        # dataList
        datalist, numrows, ids = resource.datalist(fields=list_fields,
                                                   start=start,
                                                   limit=limit,
                                                   listid=listid,
                                                   orderby=orderby,
                                                   layout=list_layout)
        # Render the list
        ajaxurl = r.url(vars={"update": widget["index"]},
                        representation="dl")
        data = datalist.html(ajaxurl=ajaxurl,
                             pagesize=pagesize,
                             empty = P(I(_class="icon-folder-open-alt"),
                                       BR(),
                                       S3CRUD.crud_string(tablename,
                                                          "msg_no_match"),
                                       _class="empty_card-holder"
                                      ),
                             )

        if representation == "dl":
            # This is an Ajax-request, so we don't need the wrapper
            current.response.view = "plain.html"
            return data

        # Interactive only below here
        label = widget.get("label", "")
        if label:
            label = T(label)
        icon = widget.get("icon", "")
        if icon:
            icon = TAG[""](I(_class=icon), " ")

        s3 = current.response.s3

        if pagesize and numrows > pagesize:
            # Button to display the rest of the records in a Modal
            more = numrows - pagesize
            vars = {}
            if context:
                filters = context.serialize_url(resource)
                for f in filters:
                    vars[f] = filters[f]
            if filter:
                filters = filter.serialize_url(resource)
                for f in filters:
                    vars[f] = filters[f]
            c, f = tablename.split("_", 1)
            url = URL(c=c, f=f, args=["datalist.popup"],
                      vars=vars)
            more = DIV(A(BUTTON("%s (%s)" % (T("see more"), more),
                                _class="btn btn-mini",
                                _type="button",
                                ),
                         _class="s3_modal",
                         _href=url,
                         _title=label,
                         ),
                       _class="more_profile")
        else:
            more = ""

        # Link for create-popup
        create_popup = self._create_popup(r,
                                          widget,
                                          listid,
                                          resource,
                                          context,
                                          numrows)

        colspan = widget.get("colspan", 1)
        if colspan == 1:
            _class = "span6"
        elif colspan == 2:
            _class = "span12"
        else:
            # Unsupported
            raise

        # Render the widget
        output = DIV(create_popup,
                     H4(icon,
                        label,
                        _class="profile-sub-header"),
                     DIV(data,
                         more,
                         _class="card-holder"),
                     _class=_class)

        return output