Features: 1) Add GatewayForm to manage Gateway model; 2) Enhance GatewayAdmin with list display, search, and ordering; 3) Introduce integration_variables field in the Gateway model; 4) Expand support for currencies with symbols via CURRENCIES_WITH_SYMBOLS;
Fixes: 1) Add missing Gateway import in forms and admin modules; 2) Correct maximum lengths for currency fields in the Gateway model; Extra: 1) Refactor README for clarity and conciseness; 2) Adjust formatting in `core/sitemaps.py` for readability.
This commit is contained in:
parent
4757634ce5
commit
3b7c405e84
6 changed files with 173 additions and 119 deletions
201
README.md
201
README.md
|
|
@ -2,153 +2,138 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
eVibes — your store without the extra baggage.
|
eVibes — a lightweight, production-ready e‑commerce backend. Storefront, product catalog, cart, and orders work out of the box. Minimal complexity, maximum flexibility — install, adjust to your needs, and start selling.
|
||||||
Everything works out of the box: storefront, product catalog, cart, and orders.
|
|
||||||
Minimal complexity, maximum flexibility — install, adjust to your needs, and start selling.
|
- Public issues: https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Features](#features)
|
- Features
|
||||||
- [Getting Started](#getting-started)
|
- Quick Start
|
||||||
- [Prerequisites](#prerequisites)
|
- Prerequisites
|
||||||
- [Installation](#installation)
|
- Installation
|
||||||
- [Configuration](#configuration)
|
- Configuration
|
||||||
- [Dockerfile](#Dockerfile)
|
- Dockerfile
|
||||||
- [nginx](#nginx)
|
- nginx
|
||||||
- [.env](#env)
|
- .env
|
||||||
- [Usage](#usage)
|
- Usage
|
||||||
- [Contact](#contact)
|
- Contributing
|
||||||
|
- Contact
|
||||||
|
- License
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Modular Architecture**: Extend and customize the backend to fit your needs.
|
- Modular backend, easy to extend and customize
|
||||||
- **Dockerized Deployment**: Quick setup and deployment using Docker and Docker Compose.
|
- Dockerized deployment with Docker Compose
|
||||||
- **Asynchronous Task Processing**: Integrated Celery workers and beat scheduler for background tasks.
|
- Celery workers and beat for background tasks
|
||||||
- **GraphQL and REST APIs**: Supports both GraphQL and RESTful API endpoints.
|
- REST and GraphQL APIs
|
||||||
- **Internationalization**: Multilingual support using modeltranslate.
|
- Internationalization with modeltranslation
|
||||||
- **Advanced Caching**: Utilizes Redis for caching and task queuing.
|
- Redis-based caching and queues
|
||||||
- **Security**: Implements JWT authentication and rate limiting.
|
- JWT auth and rate limiting
|
||||||
|
|
||||||
## Getting Started
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Docker and Docker Compose are installed on your machine.
|
- Docker and Docker Compose
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitlab.com/wiseless.xyz/eVibes.git
|
git clone https://gitlab.com/wiseless.xyz/eVibes.git
|
||||||
cd eVibes
|
cd eVibes
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Choose the storefront. By default, `main` branch has no storefront included.
|
2. Choose a storefront (optional). The `main` branch ships without a storefront. If you want one, pick a branch:
|
||||||
Skip this step if you're OK with that and plan to only use API or develop your own storefront.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout storefront-<options: nuxt, next, sk, qwik >
|
git checkout storefront-<nuxt|next|sk|qwik>
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Generate your .env file. Check and confirm the contents afterward.
|
3. Generate your .env file and review its values
|
||||||
|
- Windows
|
||||||
|
```powershell
|
||||||
|
scripts\Windows\generate-environment-file.ps1
|
||||||
|
```
|
||||||
|
- Unix
|
||||||
|
```bash
|
||||||
|
scripts/Unix/generate-environment-file.sh
|
||||||
|
```
|
||||||
|
|
||||||
- Windows
|
4. Install dependencies
|
||||||
```powershell
|
- Windows
|
||||||
scripts\Windows\generate-environment-file.ps1
|
```powershell
|
||||||
```
|
scripts\Windows\install.ps1
|
||||||
- Unix
|
```
|
||||||
```bash
|
- Unix
|
||||||
scripts/Unix/generate-environment-file.sh
|
```bash
|
||||||
```
|
scripts/Unix/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
4. Install all the dependencies.
|
5. Run the stack
|
||||||
|
- Windows
|
||||||
|
```powershell
|
||||||
|
scripts\Windows\run.ps1
|
||||||
|
```
|
||||||
|
- Unix
|
||||||
|
```bash
|
||||||
|
scripts/Unix/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
- Windows
|
6. Production checklist
|
||||||
```powershell
|
- Include `nginx.conf` into your Nginx setup
|
||||||
scripts\Windows\install.ps1
|
- Issue TLS certs with Certbot (https://certbot.eff.org/)
|
||||||
```
|
|
||||||
- Unix
|
|
||||||
```bash
|
|
||||||
scripts/Unix/install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Spin it up.
|
|
||||||
|
|
||||||
- Windows
|
|
||||||
```powershell
|
|
||||||
scripts\Windows\run.ps1
|
|
||||||
```
|
|
||||||
- Unix
|
|
||||||
```bash
|
|
||||||
scripts/Unix/run.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Bring to production.
|
|
||||||
|
|
||||||
Include `nginx` file to your nginx configuration, you really want to install and
|
|
||||||
run [Certbot](https://certbot.eff.org/) afterward!
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Dockerfile
|
### Dockerfile
|
||||||
|
If you rely on locale mirrors, adjust Debian sources before running install scripts:
|
||||||
Remember to change the
|
```
|
||||||
`RUN sed -i 's|https://deb.debian.org/debian|https://ftp.<locale>.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources`
|
RUN sed -i 's|https://deb.debian.org/debian|https://ftp.<locale>.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources
|
||||||
before running installment scripts
|
```
|
||||||
|
|
||||||
### nginx
|
### nginx
|
||||||
|
- Comment out SSL-related lines
|
||||||
Please comment-out SSL-related lines, then apply necessary configurations, run `certbot --cert-only --nginx`,
|
- Apply your domain-specific settings
|
||||||
decomment previously commented lines, and enjoy eVibes over HTTPS!
|
- Run `certbot --cert-only --nginx`
|
||||||
|
- Uncomment SSL lines and reload Nginx
|
||||||
|
|
||||||
### .env
|
### .env
|
||||||
|
After generation, review and update secrets and credentials (API keys, DB password, Redis password, etc.).
|
||||||
After .env file generation, you may want to edit some of its values, such as macroservices` API keys, database password,
|
|
||||||
redis password, etc.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- Add the necessary subdomains to DNS-settings of your domain, those are:
|
- DNS records you’ll typically want:
|
||||||
|
1. @.your-domain.com
|
||||||
|
2. www.your-domain.com
|
||||||
|
3. api.your-domain.com
|
||||||
|
4. b2b.your-domain.com
|
||||||
|
5. prometheus.your-domain.com
|
||||||
|
|
||||||
1. @.your-domain.com
|
- For local development, add hosts entries (development only):
|
||||||
2. www.your-domain.com
|
```hosts
|
||||||
3. api.your-domain.com
|
127.0.0.1 api.localhost
|
||||||
4. b2b.your-domain.com
|
127.0.0.1 b2b.localhost
|
||||||
5. prometheus.your-domain.com
|
|
||||||
|
|
||||||
- Add these lines to your hosts-file to use django-hosts functionality on localhost(*DEVELOPMENT ONLY*):
|
|
||||||
|
|
||||||
```hosts
|
|
||||||
127.0.0.1 api.localhost
|
|
||||||
127.0.0.1 b2b.localhost
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the services are up and running, you can access the application at
|
|
||||||
`http://api.your-domain.com`(http://api.localhost:8000).
|
|
||||||
|
|
||||||
- **Django Admin**: `http://api.your-domain.com/` (will redirect to admin)
|
|
||||||
- **API Docs**:
|
|
||||||
- REST API: `http://api.localhost:8000/docs/swagger` or `http://api.localhost:8000/docs/redoc`
|
|
||||||
- GraphQL API: `http://api.localhost:8000/graphql/`
|
|
||||||
|
|
||||||
## Uninstall eVibes
|
|
||||||
|
|
||||||
You are not planning to do that, aren't you?
|
|
||||||
|
|
||||||
- Windows
|
|
||||||
```powershell
|
|
||||||
scripts\Windows\uninstall.ps1
|
|
||||||
```
|
|
||||||
- Unix
|
|
||||||
```bash
|
|
||||||
scripts/Unix/uninstall.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Once running, access:
|
||||||
|
- API root / Admin redirect: http://api.localhost:8000/
|
||||||
|
- REST docs: http://api.localhost:8000/docs/swagger or http://api.localhost:8000/docs/redoc
|
||||||
|
- GraphQL: http://api.localhost:8000/graphql/
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
- Track and report issues here: https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=list
|
||||||
|
- Pull requests are welcome. Please keep changes minimal and focused.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
- **Author**: Egor "fureunoir" Gorbunov
|
- Author: Egor "fureunoir" Gorbunov
|
||||||
- Email: contact@fureunoir.com
|
- Email: contact@fureunoir.com
|
||||||
- Telegram: [@fureunoir](https://t.me/fureunoir)
|
- Telegram: https://t.me/fureunoir
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the terms of the LICENSE file included in this repository.
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -38,7 +38,9 @@ class StaticPagesSitemap(SitemapLanguageMixin, Sitemap): # type: ignore [type-a
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
for static_post_page in Post.objects.filter(is_static_page=True, is_active=True).only("title", "slug", "modified"):
|
for static_post_page in Post.objects.filter(is_static_page=True, is_active=True).only(
|
||||||
|
"title", "slug", "modified"
|
||||||
|
):
|
||||||
pages.append(
|
pages.append(
|
||||||
{
|
{
|
||||||
"name": static_post_page.title,
|
"name": static_post_page.title,
|
||||||
|
|
|
||||||
|
|
@ -222,28 +222,64 @@ LANGUAGES: tuple[tuple[str, str], ...] = (
|
||||||
|
|
||||||
LANGUAGE_CODE: str = "en-gb"
|
LANGUAGE_CODE: str = "en-gb"
|
||||||
|
|
||||||
CURRENCIES: tuple[tuple[str, str], ...] = (
|
CURRENCIES_BY_LANGUAGES: tuple[tuple[str, str], ...] = (
|
||||||
("en-gb", "EUR"),
|
|
||||||
("ar-ar", "AED"),
|
("ar-ar", "AED"),
|
||||||
("cs-cz", "CZK"),
|
("cs-cz", "CZK"),
|
||||||
("da-dk", "EUR"),
|
("da-dk", "DKK"),
|
||||||
("de-de", "EUR"),
|
("de-de", "EUR"),
|
||||||
|
("en-gb", "GBP"),
|
||||||
("en-us", "USD"),
|
("en-us", "USD"),
|
||||||
("es-es", "EUR"),
|
("es-es", "EUR"),
|
||||||
|
("fa-ir", "IRR"),
|
||||||
("fr-fr", "EUR"),
|
("fr-fr", "EUR"),
|
||||||
|
("he-il", "ILS"),
|
||||||
("hi-in", "INR"),
|
("hi-in", "INR"),
|
||||||
|
("hr-hr", "EUR"),
|
||||||
|
("id-id", "IDR"),
|
||||||
("it-it", "EUR"),
|
("it-it", "EUR"),
|
||||||
("ja-jp", "JPY"),
|
("ja-jp", "JPY"),
|
||||||
("kk-kz", "KZT"),
|
("kk-kz", "KZT"),
|
||||||
|
("ko-kr", "KRW"),
|
||||||
("nl-nl", "EUR"),
|
("nl-nl", "EUR"),
|
||||||
|
("no-no", "NOK"),
|
||||||
("pl-pl", "PLN"),
|
("pl-pl", "PLN"),
|
||||||
("pt-br", "EUR"),
|
("pt-br", "BRL"),
|
||||||
("ro-ro", "RON"),
|
("ro-ro", "RON"),
|
||||||
("ru-ru", "RUB"),
|
("ru-ru", "RUB"),
|
||||||
|
("sv-se", "SEK"),
|
||||||
|
("th-th", "THB"),
|
||||||
|
("tr-tr", "TRY"),
|
||||||
|
("vi-vn", "VND"),
|
||||||
("zh-hans", "CNY"),
|
("zh-hans", "CNY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
CURRENCY_CODE: str = dict(CURRENCIES).get(LANGUAGE_CODE) # type: ignore [assignment]
|
CURRENCIES_WITH_SYMBOLS: tuple[tuple[str, str], ...] = (
|
||||||
|
("AED", "د.إ"),
|
||||||
|
("BRL", "R$"),
|
||||||
|
("CNY", "¥"),
|
||||||
|
("CZK", "Kč"),
|
||||||
|
("DKK", "kr"),
|
||||||
|
("EUR", "€"),
|
||||||
|
("GBP", "£"),
|
||||||
|
("IDR", "Rp"),
|
||||||
|
("ILS", "₪"),
|
||||||
|
("INR", "₹"),
|
||||||
|
("IRR", "﷼"),
|
||||||
|
("JPY", "¥"),
|
||||||
|
("KRW", "₩"),
|
||||||
|
("KZT", "₸"),
|
||||||
|
("NOK", "kr"),
|
||||||
|
("PLN", "zł"),
|
||||||
|
("RON", "lei"),
|
||||||
|
("RUB", "₽"),
|
||||||
|
("SEK", "kr"),
|
||||||
|
("THB", "฿"),
|
||||||
|
("TRY", "₺"),
|
||||||
|
("USD", "$"),
|
||||||
|
("VND", "₫"),
|
||||||
|
)
|
||||||
|
|
||||||
|
CURRENCY_CODE: str = dict(CURRENCIES_BY_LANGUAGES).get(LANGUAGE_CODE) # type: ignore[assignment]
|
||||||
|
|
||||||
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple[str, ...] = (LANGUAGE_CODE, "en-us", "de-de")
|
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple[str, ...] = (LANGUAGE_CODE, "en-us", "de-de")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.admin import ActivationActionsMixin
|
from core.admin import ActivationActionsMixin
|
||||||
from payments.forms import TransactionForm
|
from payments.forms import TransactionForm, GatewayForm
|
||||||
from payments.models import Balance, Transaction
|
from payments.models import Balance, Transaction
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,3 +41,17 @@ class TransactionAdmin(ActivationActionsMixin, ModelAdmin): # type: ignore [mis
|
||||||
list_filter = ("currency", "payment_method")
|
list_filter = ("currency", "payment_method")
|
||||||
ordering = ("balance",)
|
ordering = ("balance",)
|
||||||
form = TransactionForm
|
form = TransactionForm
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayAdmin(ActivationActionsMixin, ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"name",
|
||||||
|
"can_be_used",
|
||||||
|
"is_active",
|
||||||
|
)
|
||||||
|
search_fields = (
|
||||||
|
"name",
|
||||||
|
"default_currency",
|
||||||
|
)
|
||||||
|
ordering = ("name",)
|
||||||
|
form = GatewayForm
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from core.widgets import JSONTableWidget
|
from core.widgets import JSONTableWidget
|
||||||
from payments.models import Transaction
|
from payments.models import Gateway, Transaction
|
||||||
|
|
||||||
|
|
||||||
class TransactionForm(forms.ModelForm): # type: ignore [type-arg]
|
class TransactionForm(forms.ModelForm): # type: ignore [type-arg]
|
||||||
|
|
@ -11,3 +11,12 @@ class TransactionForm(forms.ModelForm): # type: ignore [type-arg]
|
||||||
widgets = {
|
widgets = {
|
||||||
"process": JSONTableWidget(),
|
"process": JSONTableWidget(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayForm(forms.ModelForm): # type: ignore [type-arg]
|
||||||
|
class Meta:
|
||||||
|
model = Gateway
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"integration_variables": JSONTableWidget(),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,14 +85,21 @@ class Balance(NiceModel):
|
||||||
class Gateway(NiceModel):
|
class Gateway(NiceModel):
|
||||||
name = CharField(max_length=20, null=False, blank=False, verbose_name=_("name"))
|
name = CharField(max_length=20, null=False, blank=False, verbose_name=_("name"))
|
||||||
default_currency = CharField(
|
default_currency = CharField(
|
||||||
max_length=3, null=False, blank=False, verbose_name=_("default currency"), choices=settings.CURRENCIES
|
max_length=4,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("default currency"),
|
||||||
|
choices=settings.CURRENCIES_WITH_SYMBOLS,
|
||||||
)
|
)
|
||||||
currencies = CharField(
|
currencies = CharField(
|
||||||
max_length=3,
|
max_length=255,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
verbose_name=_("currencies"),
|
verbose_name=_("currencies"),
|
||||||
help_text=_(f"comma separated list of currencies supported by this gateway, choose from {settings.CURRENCIES}"),
|
help_text=_(
|
||||||
|
f"comma separated list of currencies supported by this gateway, "
|
||||||
|
f"choose from {', '.join([code for code, _ in settings.CURRENCIES_WITH_SYMBOLS])}"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
integration_path = CharField(max_length=255, null=True, blank=True)
|
integration_path = CharField(max_length=255, null=True, blank=True)
|
||||||
minimum_transaction_amount = FloatField(
|
minimum_transaction_amount = FloatField(
|
||||||
|
|
@ -116,6 +123,7 @@ class Gateway(NiceModel):
|
||||||
help_text=_("monthly sum limit of transactions' amounts. 0 means no limit"),
|
help_text=_("monthly sum limit of transactions' amounts. 0 means no limit"),
|
||||||
)
|
)
|
||||||
priority = PositiveIntegerField(null=False, blank=False, default=10, verbose_name=_("priority"), unique=True)
|
priority = PositiveIntegerField(null=False, blank=False, default=10, verbose_name=_("priority"), unique=True)
|
||||||
|
integration_variables = JSONField(null=False, blank=False, default=dict, verbose_name=_("integration variables"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue