Skip to content

Commit

Permalink
Merge pull request #76 from kitab-bazar/feature/import-export-books
Browse files Browse the repository at this point in the history
Feature/import export books
  • Loading branch information
thenav56 authored Sep 4, 2024
2 parents 5b900d9 + aaffcb6 commit fe1479b
Show file tree
Hide file tree
Showing 24 changed files with 1,259 additions and 566 deletions.
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

0 comments on commit fe1479b

Please sign in to comment.