django-mp-avtopro 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-mp-avtopro might be problematic. Click here for more details.
- django_mp_avtopro-0.0.1/MANIFEST.in +7 -0
- django_mp_avtopro-0.0.1/PKG-INFO +15 -0
- django_mp_avtopro-0.0.1/README.md +76 -0
- django_mp_avtopro-0.0.1/avtopro/__init__.py +0 -0
- django_mp_avtopro-0.0.1/avtopro/apps.py +7 -0
- django_mp_avtopro-0.0.1/avtopro/feed.py +140 -0
- django_mp_avtopro-0.0.1/django_mp_avtopro.egg-info/PKG-INFO +15 -0
- django_mp_avtopro-0.0.1/django_mp_avtopro.egg-info/SOURCES.txt +10 -0
- django_mp_avtopro-0.0.1/django_mp_avtopro.egg-info/dependency_links.txt +1 -0
- django_mp_avtopro-0.0.1/django_mp_avtopro.egg-info/top_level.txt +1 -0
- django_mp_avtopro-0.0.1/setup.cfg +4 -0
- django_mp_avtopro-0.0.1/setup.py +19 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-mp-avtopro
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Django AvtoPro export
|
|
5
|
+
Home-page: https://github.com/pmaigutyak/mp-avtopto
|
|
6
|
+
Download-URL: https://github.com/pmaigutyak/mp-avtopto/archive/0.0.1.tar.gz
|
|
7
|
+
Author: Paul Maigutyak
|
|
8
|
+
Author-email: pmaigutyak@gmail.com
|
|
9
|
+
License: MIT
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: author-email
|
|
12
|
+
Dynamic: download-url
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: license
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# MP-avtopro
|
|
2
|
+
|
|
3
|
+
Django AvtoPro price-list export.
|
|
4
|
+
|
|
5
|
+
### Installation
|
|
6
|
+
|
|
7
|
+
Install with pip:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install django-mp-avtopro
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add `avtopro` to `INSTALLED_APPS`.
|
|
14
|
+
|
|
15
|
+
Set `DOMAIN` in settings. The `host` used for absolute links is `https://<DOMAIN>`.
|
|
16
|
+
|
|
17
|
+
### Export
|
|
18
|
+
|
|
19
|
+
Subclass `avtopro.feed.ExportStrategy`. Implement `get_queryset` and every required product field. Override an optional field when the shop has that information.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from django.urls import path
|
|
23
|
+
|
|
24
|
+
from avtopro.feed import ExportStrategy
|
|
25
|
+
from products.models import Product
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProductExport(ExportStrategy):
|
|
29
|
+
|
|
30
|
+
def get_queryset(self):
|
|
31
|
+
return Product.objects.filter(is_visible=True)
|
|
32
|
+
|
|
33
|
+
def get_vendor(self, product):
|
|
34
|
+
return product.manufacturer.name
|
|
35
|
+
|
|
36
|
+
def get_price(self, product):
|
|
37
|
+
return product.price
|
|
38
|
+
|
|
39
|
+
def get_quantity(self, product):
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
def get_code(self, product):
|
|
43
|
+
return product.code
|
|
44
|
+
|
|
45
|
+
def get_description(self, product):
|
|
46
|
+
return product.description
|
|
47
|
+
|
|
48
|
+
def get_used(self, product):
|
|
49
|
+
if product.is_new:
|
|
50
|
+
return None
|
|
51
|
+
return '1'
|
|
52
|
+
|
|
53
|
+
def get_images(self, product):
|
|
54
|
+
if not product.logo:
|
|
55
|
+
return None
|
|
56
|
+
return f'{self.host}{product.logo.url}'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
urlpatterns = [
|
|
60
|
+
path('avtopro.csv', ProductExport.as_view(), name='avtopro'),
|
|
61
|
+
]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Required fields: `vendor`, `price`, `quantity`, `code`.
|
|
65
|
+
|
|
66
|
+
Optional fields stay out of the file until overridden: `description`, `used`, `images`, `restored`.
|
|
67
|
+
|
|
68
|
+
`used` is the `Б/У` column. Any non-empty value marks the part as used. Leave it empty for a new part. `restored` is the `Реставрація` column and works the same way.
|
|
69
|
+
|
|
70
|
+
A list is written as one cell joined with `;`. `get_images` can return a list of URLs. Empty values are written as blank cells.
|
|
71
|
+
|
|
72
|
+
The file is cached at `tmp/avtopro.csv` for one hour.
|
|
73
|
+
|
|
74
|
+
### Requirements
|
|
75
|
+
|
|
76
|
+
* Django
|
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import time
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.http import HttpResponse, HttpResponseNotFound
|
|
8
|
+
from django.views import View
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
FEED_FILE_NAME = 'avtopro.csv'
|
|
12
|
+
FEED_MAX_AGE = 60 * 60
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExportStrategy(View):
|
|
16
|
+
"""AvtoPro price-list export.
|
|
17
|
+
|
|
18
|
+
Subclass this view, implement get_queryset and the required product
|
|
19
|
+
field methods. Override an optional field when the shop has that
|
|
20
|
+
information. The shop domain is read from settings.DOMAIN.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
file_name = FEED_FILE_NAME
|
|
24
|
+
max_age = FEED_MAX_AGE
|
|
25
|
+
content_type = 'text/csv'
|
|
26
|
+
list_separator = ';'
|
|
27
|
+
product_fields = (
|
|
28
|
+
('vendor', 'Виробник'),
|
|
29
|
+
('price', 'Ціна'),
|
|
30
|
+
('quantity', 'Кількість'),
|
|
31
|
+
('code', 'Код'),
|
|
32
|
+
('description', 'Опис'),
|
|
33
|
+
('used', 'Б/У'),
|
|
34
|
+
('images', 'Фото'),
|
|
35
|
+
('restored', 'Реставрація'),
|
|
36
|
+
)
|
|
37
|
+
required_product_fields = ('vendor', 'price', 'quantity', 'code')
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def host(self):
|
|
41
|
+
return f'https://{settings.DOMAIN}'
|
|
42
|
+
|
|
43
|
+
def get_queryset(self):
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
def get_vendor(self, product):
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
def get_price(self, product):
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
def get_quantity(self, product):
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
def get_code(self, product):
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
|
|
58
|
+
def get_description(self, product):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def get_used(self, product):
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def get_images(self, product):
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def get_restored(self, product):
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def get_feed_path(self):
|
|
71
|
+
return Path(settings.BASE_DIR) / 'tmp' / self.file_name
|
|
72
|
+
|
|
73
|
+
def get(self, request, *args, **kwargs):
|
|
74
|
+
path = self.get_feed_path()
|
|
75
|
+
if self._is_stale(path):
|
|
76
|
+
self.write_feed(path)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
data = path.read_bytes()
|
|
80
|
+
except OSError:
|
|
81
|
+
return HttpResponseNotFound()
|
|
82
|
+
|
|
83
|
+
response = HttpResponse(data, content_type=self.content_type)
|
|
84
|
+
response['Content-Disposition'] = (
|
|
85
|
+
f'attachment; filename="{self.file_name}"'
|
|
86
|
+
)
|
|
87
|
+
return response
|
|
88
|
+
|
|
89
|
+
def _is_stale(self, path):
|
|
90
|
+
try:
|
|
91
|
+
mtime = path.stat().st_mtime
|
|
92
|
+
except OSError:
|
|
93
|
+
return True
|
|
94
|
+
return time.time() - mtime > self.max_age
|
|
95
|
+
|
|
96
|
+
def write_feed(self, path):
|
|
97
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
path.write_bytes(self.render_feed())
|
|
99
|
+
|
|
100
|
+
def render_feed(self):
|
|
101
|
+
stream = StringIO()
|
|
102
|
+
writer = csv.writer(stream)
|
|
103
|
+
columns = self._columns()
|
|
104
|
+
writer.writerow([header for _method, header in columns])
|
|
105
|
+
|
|
106
|
+
for product in self.get_queryset():
|
|
107
|
+
writer.writerow([
|
|
108
|
+
self._cell(getattr(self, method)(product))
|
|
109
|
+
for method, _header in columns
|
|
110
|
+
])
|
|
111
|
+
|
|
112
|
+
return stream.getvalue().encode('utf-8')
|
|
113
|
+
|
|
114
|
+
def _columns(self):
|
|
115
|
+
columns = []
|
|
116
|
+
for key, header in self.product_fields:
|
|
117
|
+
method = f'get_{key}'
|
|
118
|
+
if (
|
|
119
|
+
key in self.required_product_fields
|
|
120
|
+
or self._overridden(method)
|
|
121
|
+
):
|
|
122
|
+
columns.append((method, header))
|
|
123
|
+
return columns
|
|
124
|
+
|
|
125
|
+
def _overridden(self, method):
|
|
126
|
+
return getattr(type(self), method) is not getattr(
|
|
127
|
+
ExportStrategy, method
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _cell(self, value):
|
|
131
|
+
if isinstance(value, (list, tuple)):
|
|
132
|
+
parts = [
|
|
133
|
+
str(item)
|
|
134
|
+
for item in value
|
|
135
|
+
if item is not None and item != ''
|
|
136
|
+
]
|
|
137
|
+
value = self.list_separator.join(parts)
|
|
138
|
+
if value is None:
|
|
139
|
+
return ''
|
|
140
|
+
return value
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-mp-avtopro
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Django AvtoPro export
|
|
5
|
+
Home-page: https://github.com/pmaigutyak/mp-avtopto
|
|
6
|
+
Download-URL: https://github.com/pmaigutyak/mp-avtopto/archive/0.0.1.tar.gz
|
|
7
|
+
Author: Paul Maigutyak
|
|
8
|
+
Author-email: pmaigutyak@gmail.com
|
|
9
|
+
License: MIT
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: author-email
|
|
12
|
+
Dynamic: download-url
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: license
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
avtopro/__init__.py
|
|
5
|
+
avtopro/apps.py
|
|
6
|
+
avtopro/feed.py
|
|
7
|
+
django_mp_avtopro.egg-info/PKG-INFO
|
|
8
|
+
django_mp_avtopro.egg-info/SOURCES.txt
|
|
9
|
+
django_mp_avtopro.egg-info/dependency_links.txt
|
|
10
|
+
django_mp_avtopro.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
avtopro
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
version = '0.0.1'
|
|
5
|
+
url = 'https://github.com/pmaigutyak/mp-avtopto'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name='django-mp-avtopro',
|
|
10
|
+
version=version,
|
|
11
|
+
description='Django AvtoPro export',
|
|
12
|
+
author='Paul Maigutyak',
|
|
13
|
+
author_email='pmaigutyak@gmail.com',
|
|
14
|
+
url=url,
|
|
15
|
+
download_url='{}/archive/{}.tar.gz'.format(url, version),
|
|
16
|
+
packages=find_packages(),
|
|
17
|
+
include_package_data=True,
|
|
18
|
+
license='MIT',
|
|
19
|
+
)
|