예제 #1
0
def test_single_page_does_not_include_any_pagination_controls():
    """
    When there is only a single page, no pagination controls should render.
    """
    url = URL("/")
    controls = get_page_controls(url, current_page=1, total_pages=1)
    assert controls == []
예제 #2
0
def test_last_page_in_pagination_controls():
    """
    Last page in pagination controls, should render as:

    Previous 1 2 3 4 [5] Next
    """
    url = URL("/?page=5")
    controls = get_page_controls(url, current_page=5, total_pages=5)
    assert controls == [
        PageControl(text="Previous", url=URL("/?page=4")),
        PageControl(text="1", url=URL("/")),
        PageControl(text="2", url=URL("/?page=2")),
        PageControl(text="3", url=URL("/?page=3")),
        PageControl(text="4", url=URL("/?page=4")),
        PageControl(text="5", url=URL("/?page=5"), is_active=True),
        PageControl(text="Next", is_disabled=True),
    ]
예제 #3
0
def test_middle_page_in_pagination_controls():
    """
    Middle page in pagination controls, should render as:

    Previous 1 2 [3] 4 5 Next
    """
    url = URL("/?page=3")
    controls = get_page_controls(url, current_page=3, total_pages=5)
    assert controls == [
        PageControl(text="Previous", url=URL("/?page=2")),
        PageControl(text="1", url=URL("/")),
        PageControl(text="2", url=URL("/?page=2")),
        PageControl(text="3", is_active=True, url=URL("/?page=3")),
        PageControl(text="4", url=URL("/?page=4")),
        PageControl(text="5", url=URL("/?page=5")),
        PageControl(text="Next", url=URL("/?page=4")),
    ]
예제 #4
0
def test_second_page_in_pagination_controls():
    """
    Second page in pagination controls, should render as:

    Previous 1 [2] 3 4 5 Next
    """
    url = URL("/")
    controls = get_page_controls(url, current_page=2, total_pages=5)
    assert controls == [
        PageControl(text="Previous", url=URL("/")),  # No query parameter needed.
        PageControl(text="1", url=URL("/")),
        PageControl(text="2", is_active=True, url=URL("/?page=2")),
        PageControl(text="3", url=URL("/?page=3")),
        PageControl(text="4", url=URL("/?page=4")),
        PageControl(text="5", url=URL("/?page=5")),
        PageControl(text="Next", url=URL("/?page=3")),
    ]
예제 #5
0
def test_first_page_in_long_pagination_controls():
    """
    First page in long pagination controls, should render as:

    Previous [1] 2 3 4 5 ... 49 50 Next
    """
    url = URL("/")
    controls = get_page_controls(url, current_page=1, total_pages=50)
    assert controls == [
        PageControl(text="Previous", is_disabled=True),
        PageControl(text="1", is_active=True, url=URL("/")),
        PageControl(text="2", url=URL("/?page=2")),
        PageControl(text="3", url=URL("/?page=3")),
        PageControl(text="4", url=URL("/?page=4")),
        PageControl(text="5", url=URL("/?page=5")),
        PageControl(text="…", is_disabled=True),
        PageControl(text="49", url=URL("/?page=49")),
        PageControl(text="50", url=URL("/?page=50")),
        PageControl(text="Next", url=URL("/?page=2")),
    ]
예제 #6
0
def test_last_page_in_long_pagination_controls():
    """
    Last page in long pagination controls, should render as:

    Previous 1 2 ... 46 47 48 49 [50] Next
    """
    url = URL("/?page=50")
    controls = get_page_controls(url, current_page=50, total_pages=50)
    assert controls == [
        PageControl(text="Previous", url=URL("/?page=49")),
        PageControl(text="1", url=URL("/")),
        PageControl(text="2", url=URL("/?page=2")),
        PageControl(text="…", is_disabled=True),
        PageControl(text="46", url=URL("/?page=46")),
        PageControl(text="47", url=URL("/?page=47")),
        PageControl(text="48", url=URL("/?page=48")),
        PageControl(text="49", url=URL("/?page=49")),
        PageControl(text="50", is_active=True, url=URL("/?page=50")),
        PageControl(text="Next", is_disabled=True),
    ]
예제 #7
0
def test_ellipsis_fill_in():
    """
    If an ellipsis marker can be replaced with a single page marker, then
    we should do so.
    """
    url = URL("/?page=6")
    controls = get_page_controls(url, current_page=6, total_pages=11)
    assert controls == [
        PageControl(text="Previous", url=URL("/?page=5")),
        PageControl(text="1", url=URL("/")),
        PageControl(text="2", url=URL("/?page=2")),
        PageControl(text="3", url=URL("/?page=3")),  # Ellipsis fill-in case.
        PageControl(text="4", url=URL("/?page=4")),
        PageControl(text="5", url=URL("/?page=5")),
        PageControl(text="6", url=URL("/?page=6"), is_active=True),
        PageControl(text="7", url=URL("/?page=7")),
        PageControl(text="8", url=URL("/?page=8")),
        PageControl(text="9", url=URL("/?page=9")),  # Ellipsis fill-in case.
        PageControl(text="10", url=URL("/?page=10")),
        PageControl(text="11", url=URL("/?page=11")),
        PageControl(text="Next", url=URL("/?page=7")),
    ]
예제 #8
0
async def table(request):
    PAGE_SIZE = 10

    username = request.path_params["username"]
    table_id = request.path_params["table_id"]
    can_edit = check_can_edit(request, username)
    datasource = await load_datasource_or_404(username, table_id)

    # datasource = ElectionDataSource(app=app, year=year)
    columns = {
        key: field.title
        for key, field in datasource.schema.fields.items()
    }

    # Get some normalised information from URL query parameters
    current_page = pagination.get_page_number(url=request.url)
    order_column, is_reverse = ordering.get_ordering(url=request.url,
                                                     columns=columns)
    search_term = search.get_search_term(url=request.url)

    # Filter by any search term
    datasource = datasource.search(search_term)

    # Determine pagination info
    count = await datasource.count()
    total_pages = max(math.ceil(count / PAGE_SIZE), 1)
    current_page = max(min(current_page, total_pages), 1)
    offset = (current_page - 1) * PAGE_SIZE

    # Perform column ordering
    if order_column is not None:
        datasource = datasource.order_by(column=order_column,
                                         reverse=is_reverse)

    # Export
    export = request.query_params.get("export")
    if export == "json":
        queryset = await datasource.all()
        data = [{
            key: field.serialize(item.get(key))
            for key, field in datasource.schema.fields.items()
        } for item in queryset]
        content = json.dumps(data, indent=4)
        headers = {
            "Content-Disposition": f'attachment; filename="{table_id}.json"'
        }
        return Response(content, headers=headers)
    elif export == "csv":
        output = io.StringIO()
        writer = csv.writer(output)
        queryset = await datasource.all()

        headers = [field.title for field in datasource.schema.fields.values()]
        writer.writerow(headers)
        for item in queryset:
            row = [
                item.get(key, default="")
                for key in datasource.schema.fields.keys()
            ]
            writer.writerow(row)

        content = output.getvalue()
        headers = {
            "Content-Disposition": f'attachment; filename="{table_id}.csv"'
        }
        return Response(content, headers=headers)

    #  Perform pagination
    datasource = datasource.offset(offset).limit(PAGE_SIZE)
    queryset = await datasource.all()

    # Get pagination and column controls to render on the page
    column_controls = ordering.get_column_controls(
        url=request.url,
        columns=columns,
        selected_column=order_column,
        is_reverse=is_reverse,
    )
    page_controls = pagination.get_page_controls(url=request.url,
                                                 current_page=current_page,
                                                 total_pages=total_pages)

    if request.method == "POST":
        form_values = await request.form()
        validated_data, form_errors = datasource.validate(form_values)
        if not form_errors:
            await datasource.create(values=validated_data)
            return RedirectResponse(url=request.url, status_code=303)
        status_code = 400
    else:
        form_values = None
        form_errors = None
        status_code = 200

    accept = request.headers.get("Accept", "*/*")
    media_type = negotiate(accept, ["application/json", "text/html"])

    view_style = request.query_params.get("view")
    if view_style not in ("json", "table"):
        view_style = "table"

    json_data = None
    if view_style == "json" or media_type == "application/json":
        data = [{
            key: field.serialize(item.get(key))
            for key, field in datasource.schema.fields.items()
        } for item in queryset]
        if media_type == "application/json":
            return JSONResponse(data,
                                headers={"Access-Control-Allow-Origin": "*"})
        json_data = json.dumps(data, indent=4)

    # Render the page
    template = "table.html"
    context = {
        "request": request,
        "schema": datasource.schema,
        "owner": username,
        "table_id": table_id,
        "table_name": datasource.name,
        "table_url": datasource.url,
        "table_has_columns": bool(datasource.schema.fields),
        "table_has_rows": search_term or list(queryset),
        "queryset": queryset,
        "json_data": json_data,
        "view_style": view_style,
        "search_term": search_term,
        "column_controls": column_controls,
        "page_controls": page_controls,
        "form_errors": form_errors,
        "form_values": form_values,
        "can_edit": can_edit,
    }
    return templates.TemplateResponse(template,
                                      context,
                                      status_code=status_code)