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 == []
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), ]
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")), ]
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")), ]
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")), ]
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), ]
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")), ]
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)