Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/import export books #76

Merged
merged 10 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ jobs:
env:
DOCKER_IMAGE_SERVER: ${{ steps.prep.outputs.tagged_image }}
run: |
docker-compose -f gh-docker-compose.yml run --rm server wait-for-it db:5432 &&
docker-compose -f gh-docker-compose.yml run --rm server ./manage.py graphql_schema --out /ci-share/schema-latest.graphql &&
docker compose -f gh-docker-compose.yml run --rm server wait-for-it db:5432 &&
docker compose -f gh-docker-compose.yml run --rm server ./manage.py graphql_schema --out /ci-share/schema-latest.graphql &&
cmp --silent schema.graphql ./ci-share/schema-latest.graphql || {
echo 'The schema.graphql is not up to date with the latest changes. Please update and push latest';
diff schema.graphql schema-latest.graphql;
Expand All @@ -60,7 +60,7 @@ jobs:
env:
DOCKER_IMAGE_SERVER: ${{ steps.prep.outputs.tagged_image }}
run: |
docker-compose -f gh-docker-compose.yml run --rm server python3 manage.py makemigrations --check --dry-run || {
docker compose -f gh-docker-compose.yml run --rm server python3 manage.py makemigrations --check --dry-run || {
echo 'There are some changes to be reflected in the migration. Make sure to run makemigrations';
exit 1;
}
Expand All @@ -70,7 +70,7 @@ jobs:
CC_TEST_REPORTER_ID: ${{ secrets.CODE_CLIMATE_ID }}
DOCKER_IMAGE_SERVER: ${{ steps.prep.outputs.tagged_image }}
with:
coverageCommand: docker-compose -f gh-docker-compose.yml run --rm server /code/scripts/run_tests.sh
coverageCommand: docker compose -f gh-docker-compose.yml run --rm server /code/scripts/run_tests.sh
coverageLocations: |
${{github.workspace}}/coverage/coverage.xml:coverage.py

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
## Run development server:

```bash
docker-compose up --build
docker compose up --build
```
59 changes: 59 additions & 0 deletions apps/book/management/commands/export_books_to_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import csv
from sys import stdout
from argparse import FileType
from django.conf import settings
from django.core.management import BaseCommand
from django.contrib.postgres.aggregates import StringAgg
from django.db import models

from apps.book.models import Book


class Command(BaseCommand):
help = 'Export client translation data to csv'

def add_arguments(self, parser):
parser.add_argument('output_file', nargs='?', type=FileType('w'), default=stdout)

def handle(self, *_, **options):
output_file = options['output_file']

# TODO: Sync this with export
headers = [
'title_en',
'title_ne',
'edition',
'grade',
'author_name_en',
'author_name_ne',
'price',
'description_en',
'description_ne',
'categories_en',
'categories_ne',
'isbn',
'number_of_pages',
'language',
'publisher_en',
'publisher_ne',
'image',
'published_date',
# 'illustrator_name_en',
# 'illustrator_name_ne',
# 'editor_en',
# 'editor_ne',
]

# Collect all data to csv
writer = csv.DictWriter(output_file, fieldnames=headers)
writer.writeheader()
for row in Book.objects.annotate(
author_name_en=StringAgg('authors__name_en', ',', distinct=True),
author_name_ne=StringAgg('authors__name_ne', ',', distinct=True),
categories_en=StringAgg('categories__name_en', ',', distinct=True),
categories_ne=StringAgg('categories__name_ne', ',', distinct=True),
publisher_en=models.F('publisher__name_en'),
publisher_ne=models.F('publisher__name_ne'),
).values(*headers).distinct():
row['image'] = f"https://{settings.DJANGO_API_HOST}{settings.STATIC_URL}{row['image']}"
writer.writerow(row)
22 changes: 14 additions & 8 deletions apps/book/management/commands/import_books_from_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,25 @@ def _write_file(r, fp):
'1': Book.Grade.GRADE_1,
'2': Book.Grade.GRADE_2,
'3': Book.Grade.GRADE_3,
'4': Book.Grade.GRADE_4,
'5': Book.Grade.GRADE_5,
}

LANGUAGE_MAP = {
'English': Book.LanguageType.ENGLISH,
'Nepali': Book.LanguageType.NEPALI,
'Maithali': Book.LanguageType.MAITHALI,
'Tharu': Book.LanguageType.THARU,
'BILINGUAL': Book.LanguageType.BILINGUAL,
}


class Command(BaseCommand):
help = 'Load books from csv'
"""
# WIP: This is for one time use only.
docker-compose exec server ./manage.py loaddata provinces districts municipalities
cat data.csv | docker-compose exec -T server ./manage.py import_books_from_csv http://172.17.0.1:8080
docker compose exec server ./manage.py loaddata provinces districts municipalities
cat data.csv | docker compose exec -T server ./manage.py import_books_from_csv http://172.17.0.1:8080
"""

def add_arguments(self, parser):
Expand Down Expand Up @@ -110,7 +113,7 @@ def handle(self, *args, **options):
for row in reader:
# This is in nepali eg: 2077, convert to english
published_date = timezone.datetime(
year=int(row['Published date']) - 57,
year=int(row.get('Published date') or 2080) - 57,
month=1,
day=1,
).date()
Expand All @@ -123,7 +126,7 @@ def handle(self, *args, **options):
models.Q(title_en=title_en, publisher__name=publisher_name)
).exists():
self.stdout.write(
self.style.WARNING(f' - Book {title_en}/{isbn} already exists for publisher: {publisher_name}')
self.style.WARNING(f' - Book: {title_en}/{isbn} already exists for publisher: {publisher_name}')
)

new_book = Book.objects.create(
Expand All @@ -135,10 +138,11 @@ def handle(self, *args, **options):
description_en=row['About the book'],
description_ne=row['About the book (Nepali)'],
isbn=isbn,
price=row['Price of the book'] or 0,
price=int(float(row['Price of the book'])) or 0,
number_of_pages=(row['Number of pages'] or '0').replace('+', ''),
language=LANGUAGE_MAP[str(row['Language'])],
publisher_id=publisher_lookup.get_id_by_name(publisher_name, ne=row['Publisher (Nepali)']),
# publisher_id=publisher_lookup.get_id_by_name(publisher_name, ne=row['Publisher (Nepali)']),
publisher_id=publisher_lookup.get_id_by_name(publisher_name),
is_published=True,
)
new_book.categories.set([
Expand All @@ -153,11 +157,13 @@ def handle(self, *args, **options):
ne=row['Author’s name (Nepali)']
)
])
s_n = row['S.N']
# s_n = row['S.N']
image = row['image']
new_book.image.save(
f'{new_book.title}.jpg',
fetch_image_from_url(
f'{images_source_uri}/{publisher_name}/{s_n}.jpg'
# f'{images_source_uri}/{publisher_name}/{s_n}.jpg'
f'{images_source_uri}/{image}'
)
)
self.stdout.write(f'- {new_book}')
Expand Down
18 changes: 18 additions & 0 deletions apps/book/migrations/0007_alter_book_language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2023-12-03 15:05

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('book', '0006_auto_20230119_1225'),
]

operations = [
migrations.AlterField(
model_name='book',
name='language',
field=models.CharField(choices=[('english', 'English'), ('nepali', 'Nepali'), ('Maithali', 'Maithali'), ('Tharu', 'Tharu'), ('bilingual', 'Bilingual')], max_length=40, verbose_name='Language'),
),
]
5 changes: 3 additions & 2 deletions apps/book/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class LanguageType(models.TextChoices):
NEPALI = 'nepali', _('Nepali')
MAITHALI = 'Maithali', _('Maithali')
THARU = 'Tharu', _('Tharu')
BILINGUAL = 'bilingual', _('BILINGUAL')
BILINGUAL = 'bilingual', _('Bilingual')

class Grade(models.TextChoices):
ECD = 'ecd', _('ECD')
Expand Down Expand Up @@ -100,7 +100,8 @@ class Grade(models.TextChoices):
published_date = models.DateField(verbose_name=_('Published Date'))
edition = models.CharField(
max_length=255,
null=True, blank=True,
null=True,
blank=True,
verbose_name=_('Edition')
)
publisher = models.ForeignKey(
Expand Down
29 changes: 7 additions & 22 deletions apps/common/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import graphene
from graphene_django import DjangoObjectType
from graphene_django_extras import DjangoObjectField, PageGraphqlPagination
from django.db.models import Sum, Count, F, Q, Case, When
from django.db.models import Sum, Count, F, Q

from utils.graphene.types import CustomDjangoListObjectType, FileFieldType
from utils.graphene.fields import DjangoPaginatedListObjectField
Expand Down Expand Up @@ -403,12 +403,9 @@ def resolve_reports(root, info, **kwargs):

'number_of_books_on_the_platform': book_qs.count(),

'number_of_incentive_books': school_package_qs.filter(total_quantity__gte=10).aggregate(
'number_of_incentive_books': school_package_qs.aggregate(
total_incentive_books=Sum(
Case(
When(total_quantity__lte=30, then=F('total_quantity') * 4),
When(total_quantity__gt=30, then=120)
)
SchoolPackage.incentive_query_generator()
)
)['total_incentive_books'],

Expand Down Expand Up @@ -458,16 +455,7 @@ def resolve_reports(root, info, **kwargs):
).values('name').annotate(
no_of_books_ordered=Sum('schools__school_user__school_packages__total_quantity'),
no_of_incentive_books=Sum(
Case(
When(
schools__school_user__school_packages__total_quantity__lte=30,
then=F('schools__school_user__school_packages__total_quantity') * 4
),
When(
schools__school_user__school_packages__total_quantity__gt=30,
then=120
),
)
SchoolPackage.incentive_query_generator(prefix='schools__school_user__school_packages__')
),
district_id=F('id')
),
Expand Down Expand Up @@ -538,13 +526,10 @@ def resolve_reports(root, info, **kwargs):

return {
'number_of_books_ordered': order_qs.aggregate(total=Sum('book_order__quantity'))['total'],
'number_of_incentive_books': school_package_qs.filter(total_quantity__gte=10).aggregate(
'number_of_incentive_books': school_package_qs.aggregate(
total_incentive_books=Sum(
Case(
When(total_quantity__lte=30, then=F('total_quantity') * 4),
When(total_quantity__gt=30, then=120)
)
)
SchoolPackage.incentive_query_generator()
),
)['total_incentive_books'],
'payment_per_order_window': order_window_qs.values('title').annotate(
payment=Sum(F('orders__book_order__price') * F('orders__book_order__quantity')),
Expand Down
4 changes: 2 additions & 2 deletions apps/helpdesk/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@


class FaqFilter(django_filters.FilterSet):
search = django_filters.CharFilter(method='search')
search = django_filters.CharFilter(method='filter_search')

class Meta:
model = Faq
fields = ()

def search(self, queryset, name, value):
def filter_search(self, queryset, name, value):
if not value:
return queryset
return queryset.filter(Q(question__icontains=value) | Q(answer__icontains=value))
4 changes: 4 additions & 0 deletions apps/order/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class Meta:
class OrderWindowFactory(DjangoModelFactory):
title = fuzzy.FuzzyText(length=15)
description = fuzzy.FuzzyText(length=200)
enable_incentive = True
incentive_multiplier = 3
incentive_quantity_threshold = 10
incentive_max = 120

class Meta:
model = OrderWindow
47 changes: 47 additions & 0 deletions apps/order/migrations/0011_auto_20231203_2050.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 3.2.16 on 2023-12-03 15:05

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('order', '0010_auto_20230119_1225'),
]

operations = [
migrations.AddField(
model_name='orderwindow',
name='enable_incentive',
field=models.BooleanField(default=True),
preserve_default=False,
),
migrations.AddField(
model_name='orderwindow',
name='incentive_max',
field=models.PositiveSmallIntegerField(default=120, help_text='Max incentive count value. eg: 120'),
preserve_default=False,
),
migrations.AddField(
model_name='orderwindow',
name='incentive_multiplier',
field=models.PositiveSmallIntegerField(default=4, help_text='Generate incentive count by multiplying with. eg: 4'),
preserve_default=False,
),
migrations.AddField(
model_name='orderwindow',
name='incentive_quantity_threshold',
field=models.PositiveSmallIntegerField(default=10, help_text='Least quantity count required for incentive. eg: 10'),
preserve_default=False,
),
migrations.AlterField(
model_name='bookorder',
name='edition',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Edition'),
),
migrations.AlterField(
model_name='bookorder',
name='language',
field=models.CharField(blank=True, choices=[('english', 'English'), ('nepali', 'Nepali'), ('Maithali', 'Maithali'), ('Tharu', 'Tharu'), ('bilingual', 'Bilingual')], max_length=40, null=True, verbose_name='Language'),
),
]
18 changes: 17 additions & 1 deletion apps/order/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ class OrderWindowType(models.TextChoices):
type = models.CharField(
choices=OrderWindowType.choices, max_length=40, verbose_name=_('Order window type'), blank=True
)
# Incentive options
enable_incentive = models.BooleanField()
incentive_multiplier = models.PositiveSmallIntegerField(
help_text='Generate incentive count by multiplying with. eg: 4'
)
incentive_quantity_threshold = models.PositiveSmallIntegerField(
help_text='Least quantity count required for incentive. eg: 10'
)
incentive_max = models.PositiveSmallIntegerField(
help_text='Max incentive count value. eg: 120'
)

def __str__(self):
return f'{self.title} :: {self.start_date} - {self.end_date}'
Expand Down Expand Up @@ -116,7 +127,12 @@ class BookOrder(models.Model):
price = models.BigIntegerField(verbose_name=_('Price'))
quantity = models.PositiveIntegerField(verbose_name=_('Quantity'))
isbn = models.CharField(max_length=13, verbose_name=_('ISBN'))
edition = models.CharField(max_length=255, verbose_name=_('Edition'))
edition = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name=_('Edition')
)
total_price = models.BigIntegerField(verbose_name=_('Total Price'))
image = models.FileField(
upload_to='orders/books/images/', max_length=255, null=True, blank=True, default=None,
Expand Down
Loading
Loading