From 9e9126062516756bd0f8afaebe1811442314e362 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Fri, 24 Feb 2023 18:13:39 +0100 Subject: [PATCH 01/73] add license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..678845a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 LNbits + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 21877523dedaf95f582c022d392f7f29bec63864 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 24 Apr 2023 10:43:34 +0100 Subject: [PATCH 02/73] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 11b62fe..c55b8b2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Events +# Events - [LNbits](https://github.com/lnbits/lnbits) extension +For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions) ## Sell tickets for events and use the built-in scanner for registering attendants From b34fbdca7a910015495c01f2b96c0ff694623042 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 23 May 2023 10:54:53 +0100 Subject: [PATCH 03/73] make QR responsive on ticket page --- templates/events/ticket.html | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/templates/events/ticket.html b/templates/events/ticket.html index 21b7cfa..d7bc2aa 100644 --- a/templates/events/ticket.html +++ b/templates/events/ticket.html @@ -11,15 +11,12 @@ and present it for registration!
- - + + +
- Print + Print @@ -41,4 +38,4 @@ } }) -{% endblock %} +{% endblock %} \ No newline at end of file From 2c9a27aed69e2419e77a5dad23593813b6a3f6b8 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 23 May 2023 10:59:23 +0100 Subject: [PATCH 04/73] fix for desktop --- templates/events/ticket.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/events/ticket.html b/templates/events/ticket.html index d7bc2aa..e8fcd79 100644 --- a/templates/events/ticket.html +++ b/templates/events/ticket.html @@ -11,7 +11,7 @@ and present it for registration!
- +
From a4a96824c97418e6971b27152981107df4f8bc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Jun 2023 11:47:19 +0200 Subject: [PATCH 05/73] add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ From cdcba0073a5b959d1ae85595a6bd06f5c2fb6678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Jun 2023 11:47:55 +0200 Subject: [PATCH 06/73] add github release workflow --- .github/workflows/release.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3e1da5a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: release github version +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false From 1032bd9d7e78ff64c99d809d0e328eb93756f405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Jun 2023 11:57:16 +0200 Subject: [PATCH 07/73] refactor into singular models should always by singular --- crud.py | 32 ++++++++++++++++---------------- models.py | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crud.py b/crud.py index 12cc732..fc3de9d 100644 --- a/crud.py +++ b/crud.py @@ -3,14 +3,14 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash from . import db -from .models import CreateEvent, Events, Tickets +from .models import CreateEvent, Event, Ticket # TICKETS async def create_ticket( payment_hash: str, wallet: str, event: str, name: str, email: str -) -> Tickets: +) -> Ticket: await db.execute( """ INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid) @@ -38,12 +38,12 @@ async def create_ticket( return ticket -async def get_ticket(payment_hash: str) -> Optional[Tickets]: +async def get_ticket(payment_hash: str) -> Optional[Ticket]: row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,)) - return Tickets(**row) if row else None + return Ticket(**row) if row else None -async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]: +async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Ticket]: if isinstance(wallet_ids, str): wallet_ids = [wallet_ids] @@ -51,7 +51,7 @@ async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]: rows = await db.fetchall( f"SELECT * FROM events.ticket WHERE wallet IN ({q})", (*wallet_ids,) ) - return [Tickets(**row) for row in rows] + return [Ticket(**row) for row in rows] async def delete_ticket(payment_hash: str) -> None: @@ -65,7 +65,7 @@ async def delete_event_tickets(event_id: str) -> None: # EVENTS -async def create_event(data: CreateEvent) -> Events: +async def create_event(data: CreateEvent) -> Event: event_id = urlsafe_short_hash() await db.execute( """ @@ -91,7 +91,7 @@ async def create_event(data: CreateEvent) -> Events: return event -async def update_event(event_id: str, **kwargs) -> Events: +async def update_event(event_id: str, **kwargs) -> Event: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( f"UPDATE events.events SET {q} WHERE id = ?", (*kwargs.values(), event_id) @@ -101,12 +101,12 @@ async def update_event(event_id: str, **kwargs) -> Events: return event -async def get_event(event_id: str) -> Optional[Events]: +async def get_event(event_id: str) -> Optional[Event]: row = await db.fetchone("SELECT * FROM events.events WHERE id = ?", (event_id,)) - return Events(**row) if row else None + return Event(**row) if row else None -async def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]: +async def get_events(wallet_ids: Union[str, List[str]]) -> List[Event]: if isinstance(wallet_ids, str): wallet_ids = [wallet_ids] @@ -115,7 +115,7 @@ async def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]: f"SELECT * FROM events.events WHERE wallet IN ({q})", (*wallet_ids,) ) - return [Events(**row) for row in rows] + return [Event(**row) for row in rows] async def delete_event(event_id: str) -> None: @@ -125,15 +125,15 @@ async def delete_event(event_id: str) -> None: # EVENTTICKETS -async def get_event_tickets(event_id: str, wallet_id: str) -> List[Tickets]: +async def get_event_tickets(event_id: str, wallet_id: str) -> List[Ticket]: rows = await db.fetchall( "SELECT * FROM events.ticket WHERE wallet = ? AND event = ?", (wallet_id, event_id), ) - return [Tickets(**row) for row in rows] + return [Ticket(**row) for row in rows] -async def reg_ticket(ticket_id: str) -> List[Tickets]: +async def reg_ticket(ticket_id: str) -> List[Ticket]: await db.execute( "UPDATE events.ticket SET registered = ? WHERE id = ?", (True, ticket_id) ) @@ -141,4 +141,4 @@ async def reg_ticket(ticket_id: str) -> List[Tickets]: rows = await db.fetchall( "SELECT * FROM events.ticket WHERE event = ?", (ticket[1],) ) - return [Tickets(**row) for row in rows] + return [Ticket(**row) for row in rows] diff --git a/models.py b/models.py index dd38e97..b9bf7c0 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,4 @@ -from fastapi.param_functions import Query +from fastapi import Query from pydantic import BaseModel @@ -18,7 +18,7 @@ class CreateTicket(BaseModel): email: str -class Events(BaseModel): +class Event(BaseModel): id: str wallet: str name: str @@ -32,7 +32,7 @@ class Events(BaseModel): time: int -class Tickets(BaseModel): +class Ticket(BaseModel): id: str wallet: str event: str From 1d57e1ae1a1c2faadd30f358b2fc906ea713b624 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 28 Jun 2023 13:25:28 +0100 Subject: [PATCH 08/73] Fix create ticket endpoint (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create ticket when creating invoice not on check payment --------- Co-authored-by: dni ⚡ --- tasks.py | 4 ---- views_api.py | 33 ++++++++++++--------------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/tasks.py b/tasks.py index 945e2d2..68a3908 100644 --- a/tasks.py +++ b/tasks.py @@ -28,9 +28,5 @@ async def on_invoice_paid(payment: Payment) -> None: await api_ticket_send_ticket( payment.memo, payment.payment_hash, - CreateTicket( - name=str(payment.extra.get("name")), - email=str(payment.extra.get("email")), - ), ) return diff --git a/views_api.py b/views_api.py index f27485d..cad2cb6 100644 --- a/views_api.py +++ b/views_api.py @@ -3,9 +3,8 @@ from http import HTTPStatus from fastapi import Depends, Query from starlette.exceptions import HTTPException -from lnbits.core.crud import get_user +from lnbits.core.crud import get_user, get_standalone_payment from lnbits.core.services import create_invoice -from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type from . import events_ext @@ -110,13 +109,14 @@ async def api_ticket_make_ticket(event_id, name, email): memo=f"{event_id}", extra={"tag": "events", "name": name, "email": email}, ) + await create_ticket(payment_hash=payment_hash, wallet=event.wallet, event=event.id, name=name, email=email) except Exception as e: raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) return {"payment_hash": payment_hash, "payment_request": payment_request} @events_ext.post("/api/v1/tickets/{event_id}/{payment_hash}") -async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket): +async def api_ticket_send_ticket(event_id, payment_hash): event = await get_event(event_id) if not event: raise HTTPException( @@ -124,26 +124,17 @@ async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket): detail="Event could not be fetched.", ) - status = await api_payment(payment_hash) - if status["paid"]: - - exists = await get_ticket(payment_hash) - if exists: - return {"paid": True, "ticket_id": exists.id} - - ticket = await create_ticket( - payment_hash=payment_hash, - wallet=event.wallet, - event=event_id, - name=data.name, - email=data.email, + ticket = await get_ticket(payment_hash) + if not ticket: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Ticket could not be fetched.", ) - if not ticket: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail="Event could not be fetched.", - ) + + payment = await get_standalone_payment(payment_hash) + if not payment.pending and event.price_per_ticket * 1000 == payment.amount: return {"paid": True, "ticket_id": ticket.id} + return {"paid": False} From 458616401646cec186746670626e2db73e3ab522 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 4 Jul 2023 08:17:31 +0100 Subject: [PATCH 09/73] Fix updating event (#9) * passing a copy of original data to dialog data --- templates/events/index.html | 16 +++------------- templates/events/ticket.html | 10 +++++++--- views_api.py | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/templates/events/index.html b/templates/events/index.html index 2125893..57afb19 100644 --- a/templates/events/index.html +++ b/templates/events/index.html @@ -387,9 +387,7 @@ this.g.user.wallets[0].inkey ) .then(function (response) { - console.log(response) self.tickets = response.data.map(function (obj) { - console.log(obj) return mapEvents(obj) }) }) @@ -464,21 +462,13 @@ }, updateformDialog: function (formId) { var link = _.findWhere(this.events, {id: formId}) - console.log(link.id) - this.formDialog.data.id = link.id - this.formDialog.data.wallet = link.wallet - this.formDialog.data.name = link.name - this.formDialog.data.info = link.info - this.formDialog.data.closing_date = link.closing_date - this.formDialog.data.event_start_date = link.event_start_date - this.formDialog.data.event_end_date = link.event_end_date - this.formDialog.data.amount_tickets = link.amount_tickets - this.formDialog.data.price_per_ticket = link.price_per_ticket + + this.formDialog.data = {...link} + this.formDialog.show = true }, updateEvent: function (wallet, data) { var self = this - console.log(data) LNbits.api .request( diff --git a/templates/events/ticket.html b/templates/events/ticket.html index e8fcd79..e2f4166 100644 --- a/templates/events/ticket.html +++ b/templates/events/ticket.html @@ -12,11 +12,15 @@
- +
- Print + Print @@ -38,4 +42,4 @@ } }) -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/views_api.py b/views_api.py index cad2cb6..c0d9809 100644 --- a/views_api.py +++ b/views_api.py @@ -3,7 +3,7 @@ from http import HTTPStatus from fastapi import Depends, Query from starlette.exceptions import HTTPException -from lnbits.core.crud import get_user, get_standalone_payment +from lnbits.core.crud import get_standalone_payment, get_user from lnbits.core.services import create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type From c8b31d8e3f5dc73e83c62d6207f8c446ea9ffa28 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 18 Aug 2023 07:17:29 +0100 Subject: [PATCH 10/73] [FEAT] add timestamp on register (#15) * add timestamp on register --- README.md | 1 + config.json | 8 ++++---- crud.py | 3 ++- manifest.json | 14 +++++++------- migrations.py | 11 +++++++++-- models.py | 2 ++ views_api.py | 9 ++++++++- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c55b8b2..ebd7194 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Events - [LNbits](https://github.com/lnbits/lnbits) extension + For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions) ## Sell tickets for events and use the built-in scanner for registering attendants diff --git a/config.json b/config.json index a62bcc4..bdc8ba6 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { - "name": "Events", - "short_description": "Sell and register event tickets", - "tile": "/events/static/image/events.png", - "contributors": ["benarc"] + "name": "Events", + "short_description": "Sell and register event tickets", + "tile": "/events/static/image/events.png", + "contributors": ["benarc"] } diff --git a/crud.py b/crud.py index fc3de9d..00c7b72 100644 --- a/crud.py +++ b/crud.py @@ -135,7 +135,8 @@ async def get_event_tickets(event_id: str, wallet_id: str) -> List[Ticket]: async def reg_ticket(ticket_id: str) -> List[Ticket]: await db.execute( - "UPDATE events.ticket SET registered = ? WHERE id = ?", (True, ticket_id) + f"UPDATE events.ticket SET registered = ?, reg_timestamp = {db.timestamp_now} WHERE id = ?", + (True, ticket_id), ) ticket = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (ticket_id,)) rows = await db.fetchall( diff --git a/manifest.json b/manifest.json index 13726a7..cee435b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { - "repos": [ - { - "id": "events", - "organisation": "lnbits", - "repository": "events" - } - ] + "repos": [ + { + "id": "events", + "organisation": "lnbits", + "repository": "events" + } + ] } diff --git a/migrations.py b/migrations.py index 5b9d53b..77424eb 100644 --- a/migrations.py +++ b/migrations.py @@ -1,5 +1,4 @@ async def m001_initial(db): - await db.execute( """ CREATE TABLE events.events ( @@ -38,7 +37,6 @@ async def m001_initial(db): async def m002_changed(db): - await db.execute( """ CREATE TABLE events.ticket ( @@ -81,3 +79,12 @@ async def m002_changed(db): (row[0], row[1], row[2], row[3], row[4], row[5], True), ) await db.execute("DROP TABLE events.tickets") + + +async def m003_add_register_timestamp(db): + """ + Add a column to register the timestamp of ticket register + """ + await db.execute( + "ALTER TABLE events.ticket ADD COLUMN reg_timestamp TIMESTAMP;" + ) # NULL means not registered, or old ticket diff --git a/models.py b/models.py index b9bf7c0..62cba60 100644 --- a/models.py +++ b/models.py @@ -1,5 +1,6 @@ from fastapi import Query from pydantic import BaseModel +from typing import Optional class CreateEvent(BaseModel): @@ -39,5 +40,6 @@ class Ticket(BaseModel): name: str email: str registered: bool + reg_timestamp: Optional[int] paid: bool time: int diff --git a/views_api.py b/views_api.py index c0d9809..de53202 100644 --- a/views_api.py +++ b/views_api.py @@ -109,7 +109,13 @@ async def api_ticket_make_ticket(event_id, name, email): memo=f"{event_id}", extra={"tag": "events", "name": name, "email": email}, ) - await create_ticket(payment_hash=payment_hash, wallet=event.wallet, event=event.id, name=name, email=email) + await create_ticket( + payment_hash=payment_hash, + wallet=event.wallet, + event=event.id, + name=name, + email=email, + ) except Exception as e: raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) return {"payment_hash": payment_hash, "payment_request": payment_request} @@ -167,6 +173,7 @@ async def api_event_tickets(wallet_id, event_id): @events_ext.get("/api/v1/register/ticket/{ticket_id}") async def api_event_register_ticket(ticket_id): ticket = await get_ticket(ticket_id) + if not ticket: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Ticket does not exist." From 00f552c7510eef5cd7e5cb13bb87af3914029bfd Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 18 Aug 2023 07:18:44 +0100 Subject: [PATCH 11/73] Fix ticket create (#13) * don't set ticket as paid on create * use crud fn in tasks.py * create ticket is unpaid by default --- crud.py | 52 +++++++++++++++++++++++------------ tasks.py | 9 ++---- templates/events/display.html | 1 - views_api.py | 3 ++ 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/crud.py b/crud.py index 00c7b72..f347936 100644 --- a/crud.py +++ b/crud.py @@ -9,34 +9,52 @@ from .models import CreateEvent, Event, Ticket async def create_ticket( - payment_hash: str, wallet: str, event: str, name: str, email: str -) -> Ticket: + payment_hash: str, wallet: str, event: str, name: str, email: str) -> Ticket: await db.execute( """ INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid) VALUES (?, ?, ?, ?, ?, ?, ?) """, - (payment_hash, wallet, event, name, email, False, True), - ) - - # UPDATE EVENT DATA ON SOLD TICKET - eventdata = await get_event(event) - assert eventdata, "Couldn't get event from ticket being paid" - sold = eventdata.sold + 1 - amount_tickets = eventdata.amount_tickets - 1 - await db.execute( - """ - UPDATE events.events - SET sold = ?, amount_tickets = ? - WHERE id = ? - """, - (sold, amount_tickets, event), + (payment_hash, wallet, event, name, email, False, False), ) ticket = await get_ticket(payment_hash) assert ticket, "Newly created ticket couldn't be retrieved" return ticket +async def set_ticket_paid(payment_hash: str) -> Ticket: + ticket = await get_ticket(payment_hash) + assert ticket, "Ticket couldn't be retrieved" + + await db.execute( + """ + UPDATE events.ticket + SET paid = ? + WHERE id = ? + """, + (True, ticket.id), + ) + + await update_event_sold(ticket.event) + + return ticket + +async def update_event_sold(event_id: str): + event = await get_event(event_id) + assert event, "Couldn't get event from ticket being paid" + sold = event.sold + 1 + amount_tickets = event.amount_tickets - 1 + await db.execute( + """ + UPDATE events.events + SET sold = ?, amount_tickets = ? + WHERE id = ? + """, + (sold, amount_tickets, event_id), + ) + + return + async def get_ticket(payment_hash: str) -> Optional[Ticket]: row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,)) diff --git a/tasks.py b/tasks.py index 68a3908..d2ed732 100644 --- a/tasks.py +++ b/tasks.py @@ -4,8 +4,7 @@ from lnbits.core.models import Payment from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .models import CreateTicket -from .views_api import api_ticket_send_ticket +from .crud import set_ticket_paid async def wait_for_paid_invoices(): @@ -25,8 +24,6 @@ async def on_invoice_paid(payment: Payment) -> None: and payment.extra.get("name") and payment.extra.get("email") ): - await api_ticket_send_ticket( - payment.memo, - payment.payment_hash, - ) + + await set_ticket_paid(payment.payment_hash) return diff --git a/templates/events/display.html b/templates/events/display.html index 45c2aca..74fd871 100644 --- a/templates/events/display.html +++ b/templates/events/display.html @@ -86,7 +86,6 @@ {% endblock %} {% block scripts %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/events/index.html b/templates/events/index.html index 57afb19..9bd1e4c 100644 --- a/templates/events/index.html +++ b/templates/events/index.html @@ -4,9 +4,7 @@
- New Event + New Event @@ -17,19 +15,11 @@
Events
- Export to CSV + Export to CSV
- + {% raw %}