diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9c84f7eb --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +name: Тесты (shop/tests) + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Установка Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Установка Django + run: | + python -m pip install --upgrade pip + pip install Django coverage + + - name: Запуск только тестов из shop/tests/ + run: | + python manage.py test shop.tests --verbosity=2 --keepdb + + - name: Покрытие кода + run: | + coverage run --source=shop manage.py test shop.tests + coverage report -m \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4f37e584..b6fbf53e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,102 @@ + __pycache__/ +*.py[cod] +*$py.class +*.pyo +*.pyd +.Python +env/ +venv/ +.venv +ENV/ +env.bak/ +venv.bak/ +pip-log.txt +pip-delete-me.txt + +.venv*/ +env*/ +venv*/ +ENV*/ +env*/ + +# Django stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +media/ +staticfiles/ +*.sqlite3 + +.idea/ +.vscode/ +*.sublime-project +*.sublime-workspace +*.suo +*.sdf +*.opensdf +*.sln +*.user +*.userosscache +*.sln.docstates + +.vscode/ + .DS_Store +.DS_Store +.AppleDouble +.LSOverride + +._* + +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +node_modules/ +npm-debug.log +yarn-debug.log* +yarn-error.log* + +htmlcov/ +.coverage +.coverage.* +cache/ +nosetests.xml +coverage.xml +*.cover +.pytest_cache/ + +celerybeat-schedule +celerybeat.pid + +docs/_build/ + +.ipynb_checkpoints + +.mypy_cache/ +.dmypy.json +dmypy.json + +.pyre/ + +*.env .env +.env.local +.env.*.local +secret_key.txt + +*.orig +*.rej +*.bak +*.backup \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..87b2197f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Константин Захаров + +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. \ No newline at end of file diff --git a/shop/fixtures/products.yaml b/shop/fixtures/products.yaml index dbe7c6ab..f608bf49 100644 --- a/shop/fixtures/products.yaml +++ b/shop/fixtures/products.yaml @@ -1,15 +1,41 @@ - model: shop.product pk: 1 fields: - name: Стол + name: Стол письменный price: 2000 + quantity: 8 + - model: shop.product pk: 2 fields: - name: Стул + name: Стул офисный price: 1000 + quantity: 25 + - model: shop.product pk: 3 fields: - name: Табурет + name: Табурет кухонный price: 500 + quantity: 0 + +- model: shop.product + pk: 4 + fields: + name: Шкаф двухстворчатый + price: 7500 + quantity: 3 + +- model: shop.product + pk: 5 + fields: + name: Кровать односпальная + price: 12000 + quantity: 5 + +- model: shop.product + pk: 6 + fields: + name: Диван угловой + price: 35000 + quantity: 1 \ No newline at end of file diff --git a/shop/models.py b/shop/models.py index 68f38c59..b200f19b 100644 --- a/shop/models.py +++ b/shop/models.py @@ -1,12 +1,36 @@ -from django.db import models +from django.core.exceptions import ValidationError +from django.db import models, transaction # Create your models here. class Product(models.Model): name = models.CharField(max_length=200) price = models.PositiveIntegerField() + quantity = models.PositiveIntegerField(default=0) + + def can_buy(self, count=1): + return self.quantity >= count + + def buy(self, count=1): + if not self.can_buy(count): + raise ValidationError(f"Недостаточно товара {self.name} на складе") + self.quantity -= count + self.save(update_fields=['quantity']) + + def clean(self): + if self.quantity < 0: + raise ValidationError("количество не может быть отрицательным") + class Purchase(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE) person = models.CharField(max_length=200) address = models.CharField(max_length=200) - date = models.DateTimeField(auto_now_add=True) \ No newline at end of file + date = models.DateTimeField(auto_now_add=True) + + def save(self, *args, **kwargs): + if self.pk is None: # только при создании новой покупки + # Что бы откатиться если ошибки + with transaction.atomic(): + product = Product.objects.select_for_update().get(pk=self.product_id) + product.buy() # уменьшаем на 1 штуку + super().save(*args, **kwargs) diff --git a/shop/templates/shop/index.html b/shop/templates/shop/index.html index 643f03e5..4e96fa94 100644 --- a/shop/templates/shop/index.html +++ b/shop/templates/shop/index.html @@ -1,26 +1,32 @@
- +Наименование |
- Цена |
- - |
{{ p.name }} |
- {{ p.price }} |
- - |
| Наименование | +Цена | +Остаток | +Действие | +
|---|---|---|---|
| {{ p.name }} | +{{ p.price }} ₽ | +{{ p.quantity }} шт. | ++ {% if p.quantity > 0 %} + Купить + {% else %} + Нет в наличии + {% endif %} + | +