From c8bd44df016b87445f6f47a7c48ff7ea2e326d5e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Oct 2025 05:12:51 +0000 Subject: [PATCH] feat: Implement Django ImageField SHA1 hash calculator Co-authored-by: 309vslexus <309vslexus@gmail.com> --- QUICK_START.md | 131 +++++++ README.md | 256 ++++++++++++- SUMMARY.md | 111 ++++++ __init__.py | 1 + __pycache__/urls.cpython-313.pyc | Bin 0 -> 689 bytes db.sqlite3 | Bin 0 -> 135168 bytes demo.py | 112 ++++++ django_imagefield_sha1_examples.py | 339 ++++++++++++++++++ example_usage.py | 202 +++++++++++ imagehash/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 129 bytes imagehash/__pycache__/admin.cpython-313.pyc | Bin 0 -> 1530 bytes imagehash/__pycache__/apps.cpython-313.pyc | Bin 0 -> 545 bytes imagehash/__pycache__/models.cpython-313.pyc | Bin 0 -> 7853 bytes .../__pycache__/settings.cpython-313.pyc | Bin 0 -> 2431 bytes imagehash/__pycache__/urls.cpython-313.pyc | Bin 0 -> 776 bytes imagehash/__pycache__/views.cpython-313.pyc | Bin 0 -> 6954 bytes imagehash/admin.py | 20 ++ imagehash/apps.py | 7 + imagehash/migrations/0001_initial.py | 28 ++ imagehash/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 1433 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 140 bytes imagehash/models.py | 162 +++++++++ imagehash/settings.py | 99 +++++ imagehash/urls.py | 11 + imagehash/views.py | 160 +++++++++ imagehash/wsgi.py | 6 + manage.py | 22 ++ media/images/test_image.png | 0 media/images/test_image_Gi2LpIp.png | Bin 0 -> 90 bytes media/images/test_image_gqVnRcc.png | Bin 0 -> 90 bytes requirements.txt | 2 + setup_django.py | 38 ++ simple_example.py | 148 ++++++++ templates/index.html | 315 ++++++++++++++++ urls.py | 13 + 37 files changed, 2183 insertions(+), 1 deletion(-) create mode 100644 QUICK_START.md create mode 100644 SUMMARY.md create mode 100644 __init__.py create mode 100644 __pycache__/urls.cpython-313.pyc create mode 100644 db.sqlite3 create mode 100644 demo.py create mode 100644 django_imagefield_sha1_examples.py create mode 100644 example_usage.py create mode 100644 imagehash/__init__.py create mode 100644 imagehash/__pycache__/__init__.cpython-313.pyc create mode 100644 imagehash/__pycache__/admin.cpython-313.pyc create mode 100644 imagehash/__pycache__/apps.cpython-313.pyc create mode 100644 imagehash/__pycache__/models.cpython-313.pyc create mode 100644 imagehash/__pycache__/settings.cpython-313.pyc create mode 100644 imagehash/__pycache__/urls.cpython-313.pyc create mode 100644 imagehash/__pycache__/views.cpython-313.pyc create mode 100644 imagehash/admin.py create mode 100644 imagehash/apps.py create mode 100644 imagehash/migrations/0001_initial.py create mode 100644 imagehash/migrations/__init__.py create mode 100644 imagehash/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 imagehash/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 imagehash/models.py create mode 100644 imagehash/settings.py create mode 100644 imagehash/urls.py create mode 100644 imagehash/views.py create mode 100644 imagehash/wsgi.py create mode 100644 manage.py create mode 100644 media/images/test_image.png create mode 100644 media/images/test_image_Gi2LpIp.png create mode 100644 media/images/test_image_gqVnRcc.png create mode 100644 requirements.txt create mode 100644 setup_django.py create mode 100644 simple_example.py create mode 100644 templates/index.html create mode 100644 urls.py diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..b7fa6af --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,131 @@ +# πŸš€ Быстрый старт: SHA1 Ρ…Π΅Ρˆ ΠΈΠ· Django ImageField + +## Основной ΠΊΠΎΠ΄ для получСния SHA1 Ρ…Π΅ΡˆΠ° + +### 1. МодСль с ImageField ΠΈ автоматичСским вычислСниСм Ρ…Π΅ΡˆΠ° + +```python +import hashlib +from django.db import models + +class ImageWithHash(models.Model): + name = models.CharField(max_length=100) + image = models.ImageField(upload_to='images/') + image_hash = models.CharField(max_length=40, blank=True, null=True) + + def get_image_sha1_hash(self): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ SHA1 Ρ…Π΅Ρˆ содСрТимого изобраТСния""" + if not self.image: + return None + + try: + with self.image.open('rb') as image_file: + file_content = image_file.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ°: {e}") + return None + + def save(self, *args, **kwargs): + """АвтоматичСскоС вычислСниС Ρ…Π΅ΡˆΠ° ΠΏΡ€ΠΈ сохранСнии""" + if self.image: + self.image_hash = self.get_image_sha1_hash() + super().save(*args, **kwargs) +``` + +### 2. ИспользованиС + +```python +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ изобраТСния (Ρ…Π΅Ρˆ вычисляСтся автоматичСски) +image_obj = ImageWithHash( + name="МоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + image=uploaded_file +) +image_obj.save() +print(f"SHA1 Ρ…Π΅Ρˆ: {image_obj.image_hash}") + +# Π ΡƒΡ‡Π½ΠΎΠ΅ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ…Π΅ΡˆΠ° +hash_value = image_obj.get_image_sha1_hash() +print(f"SHA1 Ρ…Π΅Ρˆ: {hash_value}") +``` + +### 3. Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ + +```python +import hashlib + +def calculate_django_file_hash(django_file): + """ВычисляСт SHA1 Ρ…Π΅Ρˆ Django Ρ„Π°ΠΉΠ»Π°""" + if not django_file: + return None + + try: + with django_file.open('rb') as f: + file_content = f.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка: {e}") + return None + +# ИспользованиС +hash_value = calculate_django_file_hash(image_obj.image) +``` + +### 4. Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + +```python +# Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ +images = ImageWithHash.objects.filter(image_hash=target_hash) + +# Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² +from django.db.models import Count + +duplicate_hashes = ImageWithHash.objects.values('image_hash').annotate( + count=Count('image_hash') +).filter(count__gt=1, image_hash__isnull=False) + +for item in duplicate_hashes: + hash_value = item['image_hash'] + duplicates = ImageWithHash.objects.filter(image_hash=hash_value) + print(f"Π₯Сш {hash_value}: {duplicates.count()} ΠΊΠΎΠΏΠΈΠΉ") +``` + +## 🎯 ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ + +1. **Π₯Сш вычисляСтся для содСрТимого Ρ„Π°ΠΉΠ»Π°**, Π° Π½Π΅ для ΠΈΠΌΠ΅Π½ΠΈ ΠΈΠ»ΠΈ ΠΏΡƒΡ‚ΠΈ +2. **ΠžΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ всСгда ΠΈΠΌΠ΅ΡŽΡ‚ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΉ Ρ…Π΅Ρˆ** +3. **Π Π°Π·Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ Ρ…Π΅ΡˆΠΈ** (Π΄Π°ΠΆΠ΅ Ссли ΠΎΡ‚Π»ΠΈΡ‡Π°ΡŽΡ‚ΡΡ Π½Π° 1 Π±Π°ΠΉΡ‚) +4. **Π₯Сш Π½Π΅ зависит ΠΎΡ‚ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π°** ΠΈΠ»ΠΈ Π΅Π³ΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ² + +## πŸ”§ Запуск дСмонстрации + +```bash +# Установка зависимостСй +pip install Django Pillow + +# Π’Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΉ +python manage.py makemigrations +python manage.py migrate + +# Запуск дСмонстрации Django +python demo.py + +# Запуск простого ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π° (Π±Π΅Π· Django) +python simple_example.py +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Ρ„Π°ΠΉΠ»ΠΎΠ² + +- `imagehash/models.py` - МодСли Django с ImageField +- `imagehash/views.py` - API endpoints ΠΈ Π²Π΅Π±-интСрфСйс +- `demo.py` - Полная дСмонстрация с Django +- `simple_example.py` - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Π±Π΅Π· Django +- `templates/index.html` - Π’Π΅Π±-интСрфСйс для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + +## 🌐 API Endpoints + +- `POST /api/upload/` - Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° изобраТСния +- `GET /api/hash/{id}/` - ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ…Π΅ΡˆΠ° ΠΏΠΎ ID +- `GET /api/search/?hash={hash}` - Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ +- `GET /api/duplicates/` - Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² +- `POST /api/calculate-hash/` - БыстроС вычислСниС Ρ…Π΅ΡˆΠ° \ No newline at end of file diff --git a/README.md b/README.md index 63ace0e..a5c9030 100644 --- a/README.md +++ b/README.md @@ -1 +1,255 @@ -Hello word +# πŸ” Django ImageField SHA1 Hash Calculator + +ПолноС Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ для получСния SHA1 Ρ…Π΅ΡˆΠ° содСрТимого изобраТСния ΠΈΠ· Django ImageField с автоматичСским вычислСниСм, поиском Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² ΠΈ Π²Π΅Π±-интСрфСйсом. + +## πŸš€ ВозмоТности + +- **АвтоматичСскоС вычислСниС SHA1 Ρ…Π΅ΡˆΠ°** ΠΏΡ€ΠΈ сохранСнии изобраТСния +- **Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС Ρ…Π΅ΡˆΠ°** для ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +- **Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ** +- **Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²** ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +- **Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ** ΠΏΠΎ Ρ…Π΅ΡˆΠ°ΠΌ +- **Π’Π΅Π±-интСрфСйс** для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈ управлСния изобраТСниями +- **REST API** для ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½ΠΎΠ³ΠΎ доступа + +## πŸ“¦ Установка + +1. УстановитС зависимости: +```bash +pip install -r requirements.txt +``` + +2. Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚Π΅ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ: +```bash +python manage.py makemigrations +python manage.py migrate +``` + +3. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΡΡƒΠΏΠ΅Ρ€ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ): +```bash +python manage.py createsuperuser +``` + +4. ЗапуститС сСрвСр Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ: +```bash +python manage.py runserver +``` + +5. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€ ΠΈ ΠΏΠ΅Ρ€Π΅ΠΉΠ΄ΠΈΡ‚Π΅ ΠΏΠΎ адрСсу: http://127.0.0.1:8000/ + +## πŸ”§ ИспользованиС + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС + +```python +from imagehash.models import ImageWithHash + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ изобраТСния с автоматичСским вычислСниСм Ρ…Π΅ΡˆΠ° +image_obj = ImageWithHash( + name="МоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + image=uploaded_file +) +image_obj.save() # Π₯Сш вычисляСтся автоматичСски + +print(f"SHA1 Ρ…Π΅Ρˆ: {image_obj.image_hash}") +``` + +### Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС Ρ…Π΅ΡˆΠ° + +```python +# ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ…Π΅ΡˆΠ° ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ изобраТСния +hash_value = image_obj.get_image_sha1_hash() +print(f"SHA1 Ρ…Π΅Ρˆ: {hash_value}") + +# ОбновлСниС Ρ…Π΅ΡˆΠ° Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… +image_obj.update_hash() +``` + +### Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + +```python +# Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ +images = ImageWithHash.find_by_hash("abc123...") +for img in images: + print(f"НайдСно: {img.name}") +``` + +### Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + +```python +# Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² +duplicates = ImageWithHash.find_duplicates() +for hash_value, images in duplicates.items(): + print(f"Π₯Сш {hash_value}: {images.count()} ΠΊΠΎΠΏΠΈΠΉ") +``` + +### Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ + +```python +from imagehash.models import ImageHashUtility + +# ВычислСниС Ρ…Π΅ΡˆΠ° Django Ρ„Π°ΠΉΠ»Π° +hash_value = ImageHashUtility.calculate_django_file_hash(image_field) + +# ВычислСниС Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ +hash_value = ImageHashUtility.calculate_file_hash("/path/to/image.jpg") + +# Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +are_same = ImageHashUtility.compare_images_by_hash(image1, image2) +``` + +## 🌐 API Endpoints + +### POST /api/upload/ +Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° изобраТСния ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ SHA1 Ρ…Π΅ΡˆΠ° + +**ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:** +- `image` (file) - Ρ„Π°ΠΉΠ» изобраТСния +- `name` (string, ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ) - Π½Π°Π·Π²Π°Π½ΠΈΠ΅ изобраТСния + +**ΠžΡ‚Π²Π΅Ρ‚:** +```json +{ + "success": true, + "id": 1, + "name": "МоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + "image_url": "/media/images/image.jpg", + "sha1_hash": "abc123...", + "message": "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ ΠΈ Ρ…Π΅Ρˆ вычислСн" +} +``` + +### GET /api/hash/{image_id}/ +ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ SHA1 Ρ…Π΅ΡˆΠ° изобраТСния ΠΏΠΎ ID + +**ΠžΡ‚Π²Π΅Ρ‚:** +```json +{ + "success": true, + "id": 1, + "name": "МоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + "sha1_hash": "abc123...", + "image_url": "/media/images/image.jpg" +} +``` + +### GET /api/search/?hash={hash} +Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ SHA1 Ρ…Π΅ΡˆΡƒ + +**ΠžΡ‚Π²Π΅Ρ‚:** +```json +{ + "success": true, + "images": [ + { + "id": 1, + "name": "МоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + "image_url": "/media/images/image.jpg", + "sha1_hash": "abc123..." + } + ], + "count": 1 +} +``` + +### GET /api/duplicates/ +Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + +**ΠžΡ‚Π²Π΅Ρ‚:** +```json +{ + "success": true, + "duplicates": [ + { + "hash": "abc123...", + "count": 2, + "images": [ + { + "id": 1, + "name": "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 1", + "image_url": "/media/images/image1.jpg" + }, + { + "id": 2, + "name": "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 2", + "image_url": "/media/images/image2.jpg" + } + ] + } + ], + "total_duplicate_groups": 1 +} +``` + +### POST /api/calculate-hash/ +БыстроС вычислСниС Ρ…Π΅ΡˆΠ° Π±Π΅Π· сохранСния + +**ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:** +- `file` (file) - Ρ„Π°ΠΉΠ» изобраТСния + +**ΠžΡ‚Π²Π΅Ρ‚:** +```json +{ + "success": true, + "filename": "image.jpg", + "size": 12345, + "sha1_hash": "abc123..." +} +``` + +## πŸ§ͺ ВСстированиС + +ЗапуститС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования: + +```bash +python example_usage.py +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +imagehash/ +β”œβ”€β”€ models.py # МодСли Django с ImageField ΠΈ Ρ…Π΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ +β”œβ”€β”€ views.py # API endpoints ΠΈ Π²Π΅Π±-интСрфСйс +β”œβ”€β”€ urls.py # URL ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚Ρ‹ +β”œβ”€β”€ admin.py # Админ-панСль Django +β”œβ”€β”€ apps.py # ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ прилоТСния +β”œβ”€β”€ settings.py # Настройки Django +β”œβ”€β”€ manage.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Django +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ example_usage.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ templates/ +β”‚ └── index.html # Π’Π΅Π±-интСрфСйс +└── README.md # ДокумСнтация +``` + +## πŸ” Как это Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ + +1. **АвтоматичСскоС вычислСниС**: ΠŸΡ€ΠΈ сохранСнии ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° `ImageWithHash` Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ `save()` автоматичСски вызываСтся `get_image_sha1_hash()`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ: + - ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ Ρ„Π°ΠΉΠ» изобраТСния Π² Π±ΠΈΠ½Π°Ρ€Π½ΠΎΠΌ Ρ€Π΅ΠΆΠΈΠΌΠ΅ + - Π§ΠΈΡ‚Π°Π΅Ρ‚ всС содСрТимоС Ρ„Π°ΠΉΠ»Π° + - ВычисляСт SHA1 Ρ…Π΅Ρˆ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ `hashlib.sha1()` + - БохраняСт Ρ…Π΅Ρˆ Π² ΠΏΠΎΠ»Π΅ `image_hash` + +2. **Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС**: ΠœΠ΅Ρ‚ΠΎΠ΄ `get_image_sha1_hash()` ΠΌΠΎΠΆΠ½ΠΎ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ Π² любоС врСмя для получСния Ρ…Π΅ΡˆΠ° ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ изобраТСния + +3. **Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ**: Класс `ImageHashUtility` прСдоставляСт статичСскиС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ² + +## ⚠️ Π’Π°ΠΆΠ½Ρ‹Π΅ замСчания + +- Π₯Сш вычисляСтся для **содСрТимого Ρ„Π°ΠΉΠ»Π°**, Π° Π½Π΅ для ΠΏΡƒΡ‚ΠΈ ΠΈΠ»ΠΈ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ… +- ΠžΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ всСгда Π±ΡƒΠ΄ΡƒΡ‚ ΠΈΠΌΠ΅Ρ‚ΡŒ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΉ Ρ…Π΅Ρˆ, нСзависимо ΠΎΡ‚ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π° +- Π₯Сш Π½Π΅ измСняСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠΈ Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ Π΅Π³ΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ² +- Для Π±ΠΎΠ»ΡŒΡˆΠΈΡ… Ρ„Π°ΠΉΠ»ΠΎΠ² процСсс вычислСния Ρ…Π΅ΡˆΠ° ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π½ΡΡ‚ΡŒ врСмя + +## πŸ› οΈ Настройка + +Π’ `settings.py` ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ: + +- `FILE_UPLOAD_MAX_MEMORY_SIZE` - ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° Π² памяти +- `DATA_UPLOAD_MAX_MEMORY_SIZE` - ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Π΄Π°Π½Π½Ρ‹Ρ… для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- `MEDIA_ROOT` ΠΈ `MEDIA_URL` - настройки для ΠΌΠ΅Π΄ΠΈΠ° Ρ„Π°ΠΉΠ»ΠΎΠ² + +## πŸ“ ЛицСнзия + +Π­Ρ‚ΠΎΡ‚ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ создан Π² ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… цСлях ΠΈ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ свободно использован ΠΈ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Π½. \ No newline at end of file diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..4113782 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,111 @@ +# πŸ“‹ Π˜Ρ‚ΠΎΠ³ΠΎΠ²ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅: SHA1 Ρ…Π΅Ρˆ ΠΈΠ· Django ImageField + +## 🎯 Π§Ρ‚ΠΎ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ΠΎ + +### 1. **МодСль Django с ImageField ΠΈ автоматичСским вычислСниСм Ρ…Π΅ΡˆΠ°** +- `ImageWithHash` - модСль с полями `name`, `image`, `image_hash` +- АвтоматичСскоС вычислСниС SHA1 Ρ…Π΅ΡˆΠ° ΠΏΡ€ΠΈ сохранСнии +- ΠœΠ΅Ρ‚ΠΎΠ΄ `get_image_sha1_hash()` для Ρ€ΡƒΡ‡Π½ΠΎΠ³ΠΎ получСния Ρ…Π΅ΡˆΠ° +- ΠœΠ΅Ρ‚ΠΎΠ΄ `update_hash()` для обновлСния Ρ…Π΅ΡˆΠ° + +### 2. **Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ** +- `ImageHashUtility.calculate_django_file_hash()` - для Django Ρ„Π°ΠΉΠ»ΠΎΠ² +- `ImageHashUtility.calculate_file_hash()` - для Ρ„Π°ΠΉΠ»ΠΎΠ² ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ +- `ImageHashUtility.compare_images_by_hash()` - для сравнСния ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + +### 3. **Π€ΡƒΠ½ΠΊΡ†ΠΈΠΈ поиска ΠΈ Π°Π½Π°Π»ΠΈΠ·Π°** +- Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ: `ImageWithHash.find_by_hash()` +- Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²: `ImageWithHash.find_duplicates()` +- Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΠ°ΠΌ + +### 4. **Π’Π΅Π±-интСрфСйс ΠΈ API** +- HTML интСрфСйс для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +- REST API endpoints для всСх ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ +- Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² Ρ‡Π΅Ρ€Π΅Π· Π²Π΅Π±-интСрфСйс +- БыстроС вычислСниС Ρ…Π΅ΡˆΠ° Π±Π΅Π· сохранСния + +### 5. **ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования** +- `demo.py` - полная дСмонстрация с Django +- `simple_example.py` - простой ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Π±Π΅Π· Django +- `django_imagefield_sha1_examples.py` - всС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠ΄Π° + +## πŸš€ Быстрый старт + +```python +# 1. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ +class ImageWithHash(models.Model): + name = models.CharField(max_length=100) + image = models.ImageField(upload_to='images/') + image_hash = models.CharField(max_length=40, blank=True, null=True) + + def get_image_sha1_hash(self): + if not self.image: + return None + with self.image.open('rb') as f: + return hashlib.sha1(f.read()).hexdigest() + + def save(self, *args, **kwargs): + if self.image: + self.image_hash = self.get_image_sha1_hash() + super().save(*args, **kwargs) + +# 2. ИспользованиС +image_obj = ImageWithHash(name="ВСст", image=uploaded_file) +image_obj.save() # Π₯Сш вычисляСтся автоматичСски +print(f"SHA1: {image_obj.image_hash}") + +# 3. Π ΡƒΡ‡Π½ΠΎΠ΅ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ…Π΅ΡˆΠ° +hash_value = image_obj.get_image_sha1_hash() +``` + +## πŸ”§ Запуск + +```bash +# Установка +pip install Django Pillow + +# ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ +python manage.py makemigrations +python manage.py migrate + +# ДСмонстрация +python demo.py +``` + +## πŸ“ Π€Π°ΠΉΠ»Ρ‹ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +- `imagehash/models.py` - МодСли Django +- `imagehash/views.py` - API ΠΈ Π²Π΅Π±-интСрфСйс +- `imagehash/urls.py` - URL ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚Ρ‹ +- `demo.py` - ДСмонстрация с Django +- `simple_example.py` - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ +- `django_imagefield_sha1_examples.py` - ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠ΄Π° +- `templates/index.html` - Π’Π΅Π±-интСрфСйс + +## 🌐 API Endpoints + +- `POST /api/upload/` - Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° изобраТСния +- `GET /api/hash/{id}/` - ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ…Π΅ΡˆΠ° ΠΏΠΎ ID +- `GET /api/search/?hash={hash}` - Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ +- `GET /api/duplicates/` - Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² +- `POST /api/calculate-hash/` - БыстроС вычислСниС Ρ…Π΅ΡˆΠ° + +## βœ… ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ особСнности + +1. **АвтоматичСскоС вычислСниС** Ρ…Π΅ΡˆΠ° ΠΏΡ€ΠΈ сохранСнии +2. **Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²** ΠΏΠΎ Ρ…Π΅ΡˆΠ°ΠΌ +3. **Π’Π΅Π±-интСрфСйс** для управлСния +4. **REST API** для ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½ΠΎΠ³ΠΎ доступа +5. **Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ** для Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… случаСв +6. **Полная докумСнтация** ΠΈ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +## 🎯 Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ + +ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΎ ΠΏΠΎΠ»Π½ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с SHA1 Ρ…Π΅ΡˆΠ°ΠΌΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Django, Π²ΠΊΠ»ΡŽΡ‡Π°ΡŽΡ‰Π΅Π΅: +- МодСли с автоматичСским вычислСниСм Ρ…Π΅ΡˆΠ° +- API для всСх ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ +- Π’Π΅Π±-интСрфСйс для тСстирования +- ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +- Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΡŽ + +ВсС Π³ΠΎΡ‚ΠΎΠ²ΠΎ ΠΊ использованию! πŸš€ \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..70cf58f --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +# Django ImageField SHA1 Hash Calculator \ No newline at end of file diff --git a/__pycache__/urls.cpython-313.pyc b/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d132ebf081d45f718d8c72ca21597829e8fa5ac GIT binary patch literal 689 zcmZWm!E4h{82^&Iq-nFPP#qgZuuP__%=T2lF*=kDHBmcKhnGN!NtQ<2q`ahvJLzfj zW)Cv|1#cd``wv)pm_8UDJb8;0@4lC|wBX0%d%y4Ze!uVg;Eh!^2Q*q=|F%DK0KZr< zi_8>^6AC^+6Plb38i%p~Qo>GrXUgNTTG`}Yp-lkDUDy@z*OhsGn4 zn?R${c=`#()C{CheHG%DQO~m7Dr-^cy(aQbH+1?Pw~kk-gndio(Ub@r$6Y|_Cv4Ds z0XP3hAZAb3-q+5y2is@6gWVCj_5;bMg?G=st!#|c>mzg{5rsVeMoE?cEuGX)>fcd* jaQoxtd2wr4-1_q1e5Wy_=hDFt9lX^O0Z;+2&{_WgOpT$6 literal 0 HcmV?d00001 diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..ec9fd50292960e1c75159d78a64823d6c1edc8db GIT binary patch literal 135168 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 literal 0 HcmV?d00001 diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..34d2504 --- /dev/null +++ b/demo.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +""" +ДСмонстрация получСния SHA1 Ρ…Π΅ΡˆΠ° содСрТимого изобраТСния ΠΈΠ· Django ImageField +""" + +import os +import sys +import django +from django.core.files.uploadedfile import SimpleUploadedFile + +# Настройка Django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'imagehash.settings') +django.setup() + +from imagehash.models import ImageWithHash, ImageHashUtility + + +def create_test_image(): + """ + Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ тСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (1x1 пиксСль PNG) + """ + # ΠœΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ PNG Ρ„Π°ΠΉΠ» (1x1 пиксСль, ΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½Ρ‹ΠΉ) + png_data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82' + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Ρ„Π°ΠΉΠ» с ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ΠΌ для Django + from django.core.files.base import ContentFile + return ContentFile(png_data, name="test_image.png") + + +def main(): + """ + Основная дСмонстрация Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ + """ + print("πŸ” ДСмонстрация получСния SHA1 Ρ…Π΅ΡˆΠ° ΠΈΠ· Django ImageField") + print("=" * 60) + + try: + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ тСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ + print("1. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ тСстового изобраТСния...") + test_image = create_test_image() + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ImageWithHash + print("2. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° ImageWithHash...") + image_obj = ImageWithHash( + name="Π”Π΅ΠΌΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + image=test_image + ) + + # БохраняСм ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ (Ρ…Π΅Ρˆ вычисляСтся автоматичСски) + print("3. Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° (Ρ…Π΅Ρˆ вычисляСтся автоматичСски)...") + image_obj.save() + + print(f" βœ… ΠžΠ±ΡŠΠ΅ΠΊΡ‚ сохранСн с ID: {image_obj.id}") + print(f" βœ… SHA1 Ρ…Π΅Ρˆ: {image_obj.image_hash}") + print(f" βœ… URL изобраТСния: {image_obj.image.url}") + + # ДСмонстрация Ρ€ΡƒΡ‡Π½ΠΎΠ³ΠΎ вычислСния Ρ…Π΅ΡˆΠ° + print("\n4. Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС Ρ…Π΅ΡˆΠ°...") + manual_hash = image_obj.get_image_sha1_hash() + print(f" βœ… ВычислСнный Ρ…Π΅Ρˆ: {manual_hash}") + print(f" βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚: {manual_hash == image_obj.image_hash}") + + # ДСмонстрация ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ + print("\n5. ИспользованиС ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ...") + utility_hash = ImageHashUtility.calculate_django_file_hash(image_obj.image) + print(f" βœ… Π₯Сш Ρ‡Π΅Ρ€Π΅Π· ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρƒ: {utility_hash}") + print(f" βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚: {utility_hash == image_obj.image_hash}") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ для дСмонстрации поиска + print("\n6. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Π° изобраТСния...") + duplicate_image = ImageWithHash( + name="Π”ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ изобраТСния", + image=test_image # Π’ΠΎΡ‚ ΠΆΠ΅ Ρ„Π°ΠΉΠ» + ) + duplicate_image.save() + print(f" βœ… Π”ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ создан с ID: {duplicate_image.id}") + print(f" βœ… Π₯Сш Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Π°: {duplicate_image.image_hash}") + + # Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + print("\n7. Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ...") + found_images = ImageWithHash.find_by_hash(image_obj.image_hash) + print(f" βœ… НайдСно ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ с Ρ…Π΅ΡˆΠ΅ΠΌ {image_obj.image_hash}: {found_images.count()}") + for img in found_images: + print(f" - {img.name} (ID: {img.id})") + + # Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + print("\n8. Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²...") + duplicates = ImageWithHash.find_duplicates() + print(f" βœ… НайдСно Π³Ρ€ΡƒΠΏΠΏ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²: {len(duplicates)}") + for hash_value, images in duplicates.items(): + print(f" - Π₯Сш {hash_value}: {images.count()} ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ") + + # Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + print("\n9. Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ...") + are_same = ImageHashUtility.compare_images_by_hash(image_obj, duplicate_image) + print(f" βœ… Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅: {'Π”Π°' if are_same else 'НСт'}") + + print("\nβœ… ДСмонстрация Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ!") + print("\nπŸ“ ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для получСния SHA1 Ρ…Π΅ΡˆΠ°:") + print(" - image_obj.get_image_sha1_hash() - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ…Π΅Ρˆ изобраТСния") + print(" - image_obj.image_hash - ΠΏΠΎΠ»Π΅ с Ρ…Π΅ΡˆΠ΅ΠΌ (заполняСтся автоматичСски)") + print(" - ImageHashUtility.calculate_django_file_hash(file) - ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°") + print(" - ImageHashUtility.calculate_file_hash(path) - ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ") + + except Exception as e: + print(f"\n❌ Ошибка ΠΏΡ€ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ дСмонстрации: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/django_imagefield_sha1_examples.py b/django_imagefield_sha1_examples.py new file mode 100644 index 0000000..e60aff7 --- /dev/null +++ b/django_imagefield_sha1_examples.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python +""" +ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠ΄Π° для получСния SHA1 Ρ…Π΅ΡˆΠ° содСрТимого изобраТСния ΠΈΠ· Django ImageField +""" + +import hashlib +from django.db import models +from django.core.files.base import ContentFile + + +# ============================================================================ +# 1. ΠœΠžΠ”Π•Π›Π¬ Π‘ IMAGEFIELD И ΠΠ’Π’ΠžΠœΠΠ’Π˜Π§Π•Π‘ΠšΠ˜Πœ Π’Π«Π§Π˜Π‘Π›Π•ΠΠ˜Π•Πœ Π₯ЕША +# ============================================================================ + +class ImageWithHash(models.Model): + """ + МодСль с ImageField ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒΡŽ для получСния SHA1 Ρ…Π΅ΡˆΠ° + """ + name = models.CharField(max_length=100, verbose_name="НазваниС") + image = models.ImageField(upload_to='images/', verbose_name="Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅") + image_hash = models.CharField(max_length=40, blank=True, null=True, verbose_name="SHA1 Ρ…Π΅Ρˆ") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Π‘ΠΎΠ·Π΄Π°Π½ΠΎ") + + def get_image_sha1_hash(self): + """ + ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ SHA1 Ρ…Π΅Ρˆ содСрТимого изобраТСния + + Returns: + str: SHA1 Ρ…Π΅Ρˆ Π² ΡˆΠ΅ΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚Π΅Ρ€ΠΈΡ‡Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ ΠΈΠ»ΠΈ None Ссли Ρ„Π°ΠΉΠ» Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ + """ + if not self.image: + return None + + try: + # ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ Ρ„Π°ΠΉΠ» изобраТСния + with self.image.open('rb') as image_file: + # Π§ΠΈΡ‚Π°Π΅ΠΌ содСрТимоС Ρ„Π°ΠΉΠ»Π° + file_content = image_file.read() + + # ВычисляСм SHA1 Ρ…Π΅Ρˆ + sha1_hash = hashlib.sha1(file_content).hexdigest() + + return sha1_hash + + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ°: {e}") + return None + + def save(self, *args, **kwargs): + """ + ΠŸΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ save для автоматичСского вычислСния Ρ…Π΅ΡˆΠ° + """ + # ВычисляСм Ρ…Π΅Ρˆ ΠΏΠ΅Ρ€Π΅Π΄ сохранСниСм + if self.image: + self.image_hash = self.get_image_sha1_hash() + + super().save(*args, **kwargs) + + def update_hash(self): + """ + ΠžΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ‚ Ρ…Π΅Ρˆ изобраТСния (ΠΏΠΎΠ»Π΅Π·Π½ΠΎ Ссли Ρ„Π°ΠΉΠ» Π±Ρ‹Π» ΠΈΠ·ΠΌΠ΅Π½Π΅Π½) + """ + self.image_hash = self.get_image_sha1_hash() + self.save(update_fields=['image_hash']) + + +# ============================================================================ +# 2. Π£Π’Π˜Π›Π˜Π’ΠΠ ΠΠ«Π• ЀУНКЦИИ Π”Π›Π― Π ΠΠ‘ΠžΠ’Π« Π‘ Π₯Π•Π¨ΠΠœΠ˜ +# ============================================================================ + +class ImageHashUtility: + """ + Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹ΠΉ класс для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + + @staticmethod + def calculate_file_hash(file_path): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + + Args: + file_path (str): ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ + + Returns: + str: SHA1 Ρ…Π΅Ρˆ ΠΈΠ»ΠΈ None ΠΏΡ€ΠΈ ошибкС + """ + try: + with open(file_path, 'rb') as f: + file_content = f.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° {file_path}: {e}") + return None + + @staticmethod + def calculate_django_file_hash(django_file): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ Django Ρ„Π°ΠΉΠ»Π° (FileField/ImageField) + + Args: + django_file: ΠžΠ±ΡŠΠ΅ΠΊΡ‚ Django Ρ„Π°ΠΉΠ»Π° + + Returns: + str: SHA1 Ρ…Π΅Ρˆ ΠΈΠ»ΠΈ None ΠΏΡ€ΠΈ ошибкС + """ + if not django_file: + return None + + try: + with django_file.open('rb') as f: + file_content = f.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ° Django Ρ„Π°ΠΉΠ»Π°: {e}") + return None + + @staticmethod + def compare_images_by_hash(image1, image2): + """ + Π‘Ρ€Π°Π²Π½ΠΈΠ²Π°Π΅Ρ‚ Π΄Π²Π° изобраТСния ΠΏΠΎ ΠΈΡ… Ρ…Π΅ΡˆΠ°ΠΌ + + Args: + image1: ΠŸΠ΅Ρ€Π²ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (ImageWithHash ΠΎΠ±ΡŠΠ΅ΠΊΡ‚) + image2: Π’Ρ‚ΠΎΡ€ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (ImageWithHash ΠΎΠ±ΡŠΠ΅ΠΊΡ‚) + + Returns: + bool: True Ссли изобраТСния ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅, False Ссли Ρ€Π°Π·Π½Ρ‹Π΅ + """ + if not image1.image_hash or not image2.image_hash: + return False + + return image1.image_hash == image2.image_hash + + +# ============================================================================ +# 3. ΠŸΠ Π˜ΠœΠ•Π Π« Π˜Π‘ΠŸΠžΠ›Π¬Π—ΠžΠ’ΠΠΠ˜Π― +# ============================================================================ + +def example_basic_usage(): + """ + Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ использования + """ + print("=== Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ использования ===") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ тСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ + test_image_data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82' + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° + uploaded_file = ContentFile(test_image_data, name="test_image.png") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ изобраТСния + image_obj = ImageWithHash( + name="ВСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + image=uploaded_file + ) + + # БохраняСм (Ρ…Π΅Ρˆ Π±ΡƒΠ΄Π΅Ρ‚ вычислСн автоматичСски) + image_obj.save() + + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ создано с ID: {image_obj.id}") + print(f"SHA1 Ρ…Π΅Ρˆ: {image_obj.image_hash}") + print(f"URL изобраТСния: {image_obj.image.url}") + + return image_obj + + +def example_manual_hash_calculation(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ€ΡƒΡ‡Π½ΠΎΠ³ΠΎ вычислСния Ρ…Π΅ΡˆΠ° + """ + print("\n=== Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС Ρ…Π΅ΡˆΠ° ===") + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ВычисляСм Ρ…Π΅Ρˆ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ + hash_value = image_obj.get_image_sha1_hash() + print(f"ВычислСнный Ρ…Π΅Ρˆ: {hash_value}") + + # ОбновляСм Ρ…Π΅Ρˆ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + image_obj.update_hash() + print(f"Π₯Сш ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…: {image_obj.image_hash}") + + +def example_utility_functions(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ + """ + print("\n=== ИспользованиС ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ ===") + + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ВычислСниС Ρ…Π΅ΡˆΠ° Ρ‡Π΅Ρ€Π΅Π· ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρƒ + hash_from_utility = ImageHashUtility.calculate_django_file_hash(image_obj.image) + print(f"Π₯Сш Ρ‡Π΅Ρ€Π΅Π· ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρƒ: {hash_from_utility}") + + # Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ с Ρ…Π΅ΡˆΠ΅ΠΌ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + if hash_from_utility == image_obj.image_hash: + print("βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + else: + print("❌ Π₯Сши Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + + +def example_search_and_duplicates(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ поиска ΠΈ поиска Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + """ + print("\n=== Поиск ΠΈ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Ρ‹ ===") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ изобраТСния + original_image = ImageWithHash.objects.first() + if original_image: + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ копию с Ρ‚Π΅ΠΌ ΠΆΠ΅ содСрТимым + duplicate_image = ImageWithHash( + name="Π”ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ изобраТСния", + image=original_image.image # Π’ΠΎΡ‚ ΠΆΠ΅ Ρ„Π°ΠΉΠ» + ) + duplicate_image.save() + + print(f"Π‘ΠΎΠ·Π΄Π°Π½ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ с ID: {duplicate_image.id}") + print(f"Π₯Сш Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Π°: {duplicate_image.image_hash}") + + # Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + images_with_same_hash = ImageWithHash.objects.filter(image_hash=original_image.image_hash) + print(f"НайдСно ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ с Ρ…Π΅ΡˆΠ΅ΠΌ {original_image.image_hash}: {images_with_same_hash.count()}") + + # Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + from django.db.models import Count + duplicate_hashes = ImageWithHash.objects.values('image_hash').annotate( + count=Count('image_hash') + ).filter(count__gt=1, image_hash__isnull=False) + + print(f"НайдСно Π³Ρ€ΡƒΠΏΠΏ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²: {duplicate_hashes.count()}") + for item in duplicate_hashes: + hash_value = item['image_hash'] + duplicates = ImageWithHash.objects.filter(image_hash=hash_value) + print(f"Π₯Сш {hash_value}: {duplicates.count()} ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ") + + +def example_file_path_hash(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ вычислСния Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + """ + print("\n=== ВычислСниС Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ ===") + + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΏΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ + file_path = image_obj.image.path + print(f"ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ: {file_path}") + + # ВычисляСм Ρ…Π΅Ρˆ ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + hash_from_path = ImageHashUtility.calculate_file_hash(file_path) + print(f"Π₯Сш ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ: {hash_from_path}") + + # Π‘Ρ€Π°Π²Π½ΠΈΠ²Π°Π΅ΠΌ с Ρ…Π΅ΡˆΠ΅ΠΌ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + if hash_from_path == image_obj.image_hash: + print("βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + else: + print("❌ Π₯Сши Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + + +def example_comparison(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ сравнСния ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + print("\n=== Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ===") + + images = ImageWithHash.objects.all()[:2] + if len(images) < 2: + print("НуТно ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ 2 изобраТСния для сравнСния") + return + + img1, img2 = images[0], images[1] + + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 1: {img1.name} (Ρ…Π΅Ρˆ: {img1.image_hash})") + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 2: {img2.name} (Ρ…Π΅Ρˆ: {img2.image_hash})") + + are_same = ImageHashUtility.compare_images_by_hash(img1, img2) + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅: {'Π”Π°' if are_same else 'НСт'}") + + +# ============================================================================ +# 4. ΠžΠ‘ΠΠžΠ’ΠΠ«Π• ПРИНЦИПЫ +# ============================================================================ + +def print_principles(): + """ + Π’Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ основныС ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + print("\n" + "="*60) + print("πŸ“ ΠžΠ‘ΠΠžΠ’ΠΠ«Π• ПРИНЦИПЫ Π ΠΠ‘ΠžΠ’Π« Π‘ SHA1 Π₯Π•Π¨ΠΠœΠ˜ Π˜Π—ΠžΠ‘Π ΠΠ–Π•ΠΠ˜Π™") + print("="*60) + print("1. Π₯Сш вычисляСтся для Π‘ΠžΠ”Π•Π Π–Π˜ΠœΠžΠ“Πž Ρ„Π°ΠΉΠ»Π°, Π° Π½Π΅ для ΠΈΠΌΠ΅Π½ΠΈ ΠΈΠ»ΠΈ ΠΏΡƒΡ‚ΠΈ") + print("2. ΠžΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ ВБЕГДА ΠΈΠΌΠ΅ΡŽΡ‚ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΉ Ρ…Π΅Ρˆ") + print("3. Π Π°Π·Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ Ρ…Π΅ΡˆΠΈ (Π΄Π°ΠΆΠ΅ Ссли ΠΎΡ‚Π»ΠΈΡ‡Π°ΡŽΡ‚ΡΡ Π½Π° 1 Π±Π°ΠΉΡ‚)") + print("4. Π₯Сш НЕ зависит ΠΎΡ‚ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ Π΅Π³ΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ²") + print("5. Π₯Сш НЕ измСняСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠΈ Ρ„Π°ΠΉΠ»Π°") + print("6. Π₯Сш ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ для поиска Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²") + print("7. Π₯Сш ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ цСлостности Ρ„Π°ΠΉΠ»Π°") + print("\nπŸ”§ ΠžΠ‘ΠΠžΠ’ΠΠ«Π• ΠœΠ•Π’ΠžΠ”Π«:") + print(" - image_obj.get_image_sha1_hash() - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ…Π΅Ρˆ изобраТСния") + print(" - image_obj.image_hash - ΠΏΠΎΠ»Π΅ с Ρ…Π΅ΡˆΠ΅ΠΌ (заполняСтся автоматичСски)") + print(" - ImageHashUtility.calculate_django_file_hash(file) - ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°") + print(" - ImageHashUtility.calculate_file_hash(path) - ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ") + print("="*60) + + +if __name__ == "__main__": + print("πŸ” ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ получСния SHA1 Ρ…Π΅ΡˆΠ° ΠΈΠ· Django ImageField") + print("="*60) + + try: + # ЗапускаСм ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + example_basic_usage() + example_manual_hash_calculation() + example_utility_functions() + example_search_and_duplicates() + example_file_path_hash() + example_comparison() + + # Π’Ρ‹Π²ΠΎΠ΄ΠΈΠΌ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ + print_principles() + + print("\nβœ… ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½Ρ‹ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ!") + + except Exception as e: + print(f"\n❌ Ошибка ΠΏΡ€ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ²: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/example_usage.py b/example_usage.py new file mode 100644 index 0000000..62e450e --- /dev/null +++ b/example_usage.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +""" +ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ для получСния SHA1 Ρ…Π΅ΡˆΠ° ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +ΠΈΠ· Django ImageField +""" + +import os +import sys +import django +from django.core.files.uploadedfile import SimpleUploadedFile + +# Настройка Django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'imagehash.settings') +django.setup() + +from imagehash.models import ImageWithHash, ImageHashUtility + + +def example_basic_usage(): + """ + Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ использования + """ + print("=== Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ использования ===") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ тСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (1x1 пиксСль PNG) + test_image_data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82' + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° + uploaded_file = SimpleUploadedFile( + "test_image.png", + test_image_data, + content_type="image/png" + ) + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ изобраТСния + image_obj = ImageWithHash( + name="ВСстовоС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅", + image=uploaded_file + ) + + # БохраняСм (Ρ…Π΅Ρˆ Π±ΡƒΠ΄Π΅Ρ‚ вычислСн автоматичСски) + image_obj.save() + + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ создано с ID: {image_obj.id}") + print(f"SHA1 Ρ…Π΅Ρˆ: {image_obj.image_hash}") + print(f"URL изобраТСния: {image_obj.image.url}") + + return image_obj + + +def example_manual_hash_calculation(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ€ΡƒΡ‡Π½ΠΎΠ³ΠΎ вычислСния Ρ…Π΅ΡˆΠ° + """ + print("\n=== Π ΡƒΡ‡Π½ΠΎΠ΅ вычислСниС Ρ…Π΅ΡˆΠ° ===") + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ВычисляСм Ρ…Π΅Ρˆ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ + hash_value = image_obj.get_image_sha1_hash() + print(f"ВычислСнный Ρ…Π΅Ρˆ: {hash_value}") + + # ОбновляСм Ρ…Π΅Ρˆ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + image_obj.update_hash() + print(f"Π₯Сш ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…: {image_obj.image_hash}") + + +def example_utility_functions(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ + """ + print("\n=== ИспользованиС ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ ===") + + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ВычислСниС Ρ…Π΅ΡˆΠ° Ρ‡Π΅Ρ€Π΅Π· ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρƒ + hash_from_utility = ImageHashUtility.calculate_django_file_hash(image_obj.image) + print(f"Π₯Сш Ρ‡Π΅Ρ€Π΅Π· ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρƒ: {hash_from_utility}") + + # Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ с Ρ…Π΅ΡˆΠ΅ΠΌ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + if hash_from_utility == image_obj.image_hash: + print("βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + else: + print("❌ Π₯Сши Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + + +def example_search_and_duplicates(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ поиска ΠΈ поиска Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + """ + print("\n=== Поиск ΠΈ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Ρ‹ ===") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ изобраТСния + original_image = ImageWithHash.objects.first() + if original_image: + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ копию с Ρ‚Π΅ΠΌ ΠΆΠ΅ содСрТимым + duplicate_image = ImageWithHash( + name="Π”ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ изобраТСния", + image=original_image.image # Π’ΠΎΡ‚ ΠΆΠ΅ Ρ„Π°ΠΉΠ» + ) + duplicate_image.save() + + print(f"Π‘ΠΎΠ·Π΄Π°Π½ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ с ID: {duplicate_image.id}") + print(f"Π₯Сш Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Π°: {duplicate_image.image_hash}") + + # Поиск ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + images_with_same_hash = ImageWithHash.find_by_hash(original_image.image_hash) + print(f"НайдСно ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ с Ρ…Π΅ΡˆΠ΅ΠΌ {original_image.image_hash}: {images_with_same_hash.count()}") + + # Поиск Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² + duplicates = ImageWithHash.find_duplicates() + print(f"НайдСно Π³Ρ€ΡƒΠΏΠΏ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²: {len(duplicates)}") + + for hash_value, images in duplicates.items(): + print(f"Π₯Сш {hash_value}: {images.count()} ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ") + + +def example_file_path_hash(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ вычислСния Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + """ + print("\n=== ВычислСниС Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ ===") + + image_obj = ImageWithHash.objects.first() + if not image_obj: + print("НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…") + return + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΏΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ + file_path = image_obj.image.path + print(f"ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ: {file_path}") + + # ВычисляСм Ρ…Π΅Ρˆ ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + hash_from_path = ImageHashUtility.calculate_file_hash(file_path) + print(f"Π₯Сш ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ: {hash_from_path}") + + # Π‘Ρ€Π°Π²Π½ΠΈΠ²Π°Π΅ΠΌ с Ρ…Π΅ΡˆΠ΅ΠΌ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… + if hash_from_path == image_obj.image_hash: + print("βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + else: + print("❌ Π₯Сши Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚!") + + +def example_comparison(): + """ + ΠŸΡ€ΠΈΠΌΠ΅Ρ€ сравнСния ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + print("\n=== Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ===") + + images = ImageWithHash.objects.all()[:2] + if len(images) < 2: + print("НуТно ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ 2 изобраТСния для сравнСния") + return + + img1, img2 = images[0], images[1] + + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 1: {img1.name} (Ρ…Π΅Ρˆ: {img1.image_hash})") + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ 2: {img2.name} (Ρ…Π΅Ρˆ: {img2.image_hash})") + + are_same = ImageHashUtility.compare_images_by_hash(img1, img2) + print(f"Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅: {'Π”Π°' if are_same else 'НСт'}") + + +def cleanup_database(): + """ + ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ΠΎΡ‚ тСстовых Π΄Π°Π½Π½Ρ‹Ρ… + """ + print("\n=== ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ===") + count = ImageWithHash.objects.count() + ImageWithHash.objects.all().delete() + print(f"Π£Π΄Π°Π»Π΅Π½ΠΎ {count} записСй") + + +if __name__ == "__main__": + print("πŸ” ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования Django ImageField SHA1 Hash Calculator") + print("=" * 60) + + try: + # ЗапускаСм ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + example_basic_usage() + example_manual_hash_calculation() + example_utility_functions() + example_search_and_duplicates() + example_file_path_hash() + example_comparison() + + # ΠžΡ‡ΠΈΡ‰Π°Π΅ΠΌ Π±Π°Π·Ρƒ Π΄Π°Π½Π½Ρ‹Ρ… + cleanup_database() + + print("\nβœ… ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½Ρ‹ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ!") + + except Exception as e: + print(f"\n❌ Ошибка ΠΏΡ€ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ²: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/imagehash/__init__.py b/imagehash/__init__.py new file mode 100644 index 0000000..057b0ab --- /dev/null +++ b/imagehash/__init__.py @@ -0,0 +1 @@ +# ImageHash Django App \ No newline at end of file diff --git a/imagehash/__pycache__/__init__.cpython-313.pyc b/imagehash/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27bad2646bde5cd67aa693ea979730b59b40d28c GIT binary patch literal 129 zcmey&%ge<81V;{k%@709k3k$5V1zP0vj7=W8PXXv8U0o=6fpsLpFvW$6!gpUi?WLg z5|dN)GjkKuQ!^5aGxX!*GxIV_;^XxSDsOSvv)K3@ipQJ~A^hG8Qod FSpWzP9PJN|$s0UFw5aU;YcY8TNQN*V*=K--WYvW<|y&1k!x*c;X#yjkO> z7pxvY2t;~`lnbIgRop02pclCC53CiWS|fUaQ*Qwm(IaoxHVsvtVps=e<))l(cz}w#LYG_iAfj91qxIrX2{eQLl-pAbW)-#@lnbnog*b} zjt=Z+NRF7{DPl%2HaPE5oZ8K41AA`hhG->f#O^E0pfX~WYfk;Xs)bhie9fxx_Z+W! z&JtC7m=P$%A4tE7Y7G&W2vCzi$fTeZ5c9hf!lqWB5HZ7Mq!5BAdZXx#nXxGXaWh_u zyNO{HVkXe~5A7;BP0i#K*@(lKIR>d!vOpm{ON^8}@;^v#1ZX2z#R$uA-Kue!b`)mT z1pcwZ+cG&YtB7AFZQz#2%gpj1g*l*(hcT6{1!~|`FznI&a8DFt>eqiCmu<32S5QAL zFAN>l6v5sd9M%ZoT)m>rl8R=8m#-$zI<<-}z+T8*E#!3JZMu9RSF^61Y&&JI z`l|l&sZ-4>^Sbg>xl^fnBUi52I*uj%ul?QbHQm4C|LFhh-|lw&@BAP9UH>QlPIt=) z$wxS_si~Ahj*ZeQAN5|mbo2sN)I2;UqiM$(qRWORBcj#hAX63Y zv5#6DHU;-&2HD`Y$AG(POmHZkz%nuh97BXB6O7reB?Mzajf1Xt;0S7XT1CwqLdwDF z;}|54;qtIUe#=gL-tJ~k+<0p*`_kR)d^bD4mtFAV3owBJ-x4Du9J1n!oF%;HNZ zH|p-@&@pIi(sJEKn|W46$R|a$0a%O+8=R)+H0pzUPU7u7%X4geMIJbpTOLP3WO~Sr z&0UujRQvxCOPS?ugfLdH5;$ef(VK?cVsz^?YZsH~H*N$)9+!vm~E- zv-8ehVJ%YFj}v{iv-E5DSnv7MN}u>AOe4iU!6o1-sJLvzWTw1f)hms>-KZ;6pc*8j zNE~+ss3T<@cm?S}E|N>i9n5Wz52`JLa;l#)es(~35l*71?jY3s>}! literal 0 HcmV?d00001 diff --git a/imagehash/__pycache__/apps.cpython-313.pyc b/imagehash/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c289f170f333139981d225359678672615b36c5 GIT binary patch literal 545 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 literal 0 HcmV?d00001 diff --git a/imagehash/__pycache__/urls.cpython-313.pyc b/imagehash/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c31497ccc83220ae692d7247d6646271bbc6b042 GIT binary patch literal 776 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 He3AbH76BVV literal 0 HcmV?d00001 diff --git a/imagehash/admin.py b/imagehash/admin.py new file mode 100644 index 0000000..2eb671d --- /dev/null +++ b/imagehash/admin.py @@ -0,0 +1,20 @@ +from django.contrib import admin +from .models import ImageWithHash + + +@admin.register(ImageWithHash) +class ImageWithHashAdmin(admin.ModelAdmin): + list_display = ['name', 'image_preview', 'image_hash', 'created_at'] + list_filter = ['created_at'] + search_fields = ['name', 'image_hash'] + readonly_fields = ['image_hash', 'created_at', 'image_preview'] + + def image_preview(self, obj): + if obj.image: + return f'' + return "НСт изобраТСния" + image_preview.allow_tags = True + image_preview.short_description = "ΠŸΡ€Π΅Π²ΡŒΡŽ" + + def get_queryset(self, request): + return super().get_queryset(request).select_related() \ No newline at end of file diff --git a/imagehash/apps.py b/imagehash/apps.py new file mode 100644 index 0000000..5c3ad36 --- /dev/null +++ b/imagehash/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ImagehashConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'imagehash' + verbose_name = 'Image Hash Calculator' \ No newline at end of file diff --git a/imagehash/migrations/0001_initial.py b/imagehash/migrations/0001_initial.py new file mode 100644 index 0000000..e0cb1c5 --- /dev/null +++ b/imagehash/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2025-10-20 05:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ImageWithHash', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='НазваниС')), + ('image', models.ImageField(upload_to='images/', verbose_name='Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅')), + ('image_hash', models.CharField(blank=True, max_length=40, null=True, verbose_name='SHA1 Ρ…Π΅Ρˆ')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Π‘ΠΎΠ·Π΄Π°Π½ΠΎ')), + ], + options={ + 'verbose_name': 'Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ с Ρ…Π΅ΡˆΠ΅ΠΌ', + 'verbose_name_plural': 'Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ', + }, + ), + ] diff --git a/imagehash/migrations/__init__.py b/imagehash/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/imagehash/migrations/__pycache__/0001_initial.cpython-313.pyc b/imagehash/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb86913cdf0d5d63679b9a2f79ceb6d4da2e691 GIT binary patch literal 1433 zcmZ`(J#5=X6h2ZEt^XfevTQYu+`>q!xG){09Sjr!(gbnrHg4fFP$V^QpvAMqnj)1W z>C~BA6fL?nO93w(I(Ack+;~WX3>`WiwMe)rKqqes+@NdjD5>_~kl^Ec)c4-|?)~T_ zolXFL75{qKC@KK_BaG1%9V#c+xbg#l07@T$Rf$NeGLdJ&6nG0jxeQ=rQ(9L?PN(np zBLkg8>Pj_wER3qm5>2yCbkB5bm#Iz1z?NG(^#LF=G)@#rB;5o=f`G_SA`vJPMFS+d zsjiPuXFNHX2vjy>>*HrW4pl*v-%+T-u_fg_8OAuSa4s$m6EL|Xb4-e3CeOvBVFqSH zOzyRDWSHj}$KgbnufTCr=f;v@5l)I&S`?Px6r5g=VRI{qD8P)7q z)OmxYSDJbqe`b1(+q&CevT596^2*Js!cw~LIjBak?qP!^Tf}VYWDo7&Jr;K{F?Gv) zh*@eElP$-^$kv;fDMB6_JhGqTg8oR`^fCRF%RlK6Jq(+se~Bz?*S!W2rDstdwvHc; zg)dk0MV;sY_j*cy3HG_?AN25l@3`Nx9Nj>kGjL9XUZ}yTAHO5ACb@vOuV0xBzM+S~ zcl;R9Elam|NM7VpY~Qkmp2LG7-SY(x`V&w1nEoD8eaeXrj&C~;kZu^Ek3T1H=Te&u zz8YraWS{t#UIPriKLc#jC-kT)59mysEhB1KKGCgegk%M4b=$<2;j%HO6%PB)V6B>E zF%*^@A(lXhPrq+ruBQ;X@9UOm1GlmWZJWgPEYrre!#|9fw(0TCv6O*Z*fy|TGqFp= zda>k(}`Z+!Fa&{6<4312wTCdN$D0H~D;Qn9ZS?a9Vh@ltqMG zk1uGA7uh4`mhWK~Au_{bpYR7TO?%*w9k->|uoiYwG*UZVYi)6H@d_GpUTEzRF|(l% ziu)TE--73v@%`n#A{EsAkNYtYpXkOec48NM`Pt|B;-lSe>2jxZ`AGSDj^0DmCPB%i zIj@&59ZYtotDWg;8`9;w!Sp?v`@EN*I=Iv=&v(l6ZIiBT2jx1=HG27(gWYaL>r}M1 z@r=+mCTP$vBu{!ZhWEJb!WlY3fmA?esE4vrvdg|uCdq$gDzhq3*6*?X~4a}ML!}* a(tj$Ds{g8!lKLpo&CLb5xtD+&q4z6M?y(^N literal 0 HcmV?d00001 diff --git a/imagehash/migrations/__pycache__/__init__.cpython-313.pyc b/imagehash/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe76754a6922ebb5323f5c7065c6ab8a082a9aca GIT binary patch literal 140 zcmey&%ge<81foa2W`O9&AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl=0{qp>x?BasN zgjEsy$%s>_ZQ}Q6D literal 0 HcmV?d00001 diff --git a/imagehash/models.py b/imagehash/models.py new file mode 100644 index 0000000..3d4de11 --- /dev/null +++ b/imagehash/models.py @@ -0,0 +1,162 @@ +import hashlib +from django.db import models +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage + + +class ImageWithHash(models.Model): + """ + МодСль с ImageField ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒΡŽ для получСния SHA1 Ρ…Π΅ΡˆΠ° + """ + name = models.CharField(max_length=100, verbose_name="НазваниС") + image = models.ImageField(upload_to='images/', verbose_name="Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅") + image_hash = models.CharField(max_length=40, blank=True, null=True, verbose_name="SHA1 Ρ…Π΅Ρˆ") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Π‘ΠΎΠ·Π΄Π°Π½ΠΎ") + + class Meta: + verbose_name = "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ с Ρ…Π΅ΡˆΠ΅ΠΌ" + verbose_name_plural = "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ" + + def __str__(self): + return f"{self.name} - {self.image_hash or 'НСт Ρ…Π΅ΡˆΠ°'}" + + def get_image_sha1_hash(self): + """ + ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ SHA1 Ρ…Π΅Ρˆ содСрТимого изобраТСния + + Returns: + str: SHA1 Ρ…Π΅Ρˆ Π² ΡˆΠ΅ΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚Π΅Ρ€ΠΈΡ‡Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ ΠΈΠ»ΠΈ None Ссли Ρ„Π°ΠΉΠ» Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ + """ + if not self.image: + return None + + try: + # ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ Ρ„Π°ΠΉΠ» изобраТСния + with self.image.open('rb') as image_file: + # Π§ΠΈΡ‚Π°Π΅ΠΌ содСрТимоС Ρ„Π°ΠΉΠ»Π° + file_content = image_file.read() + + # ВычисляСм SHA1 Ρ…Π΅Ρˆ + sha1_hash = hashlib.sha1(file_content).hexdigest() + + return sha1_hash + + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ°: {e}") + return None + + def save(self, *args, **kwargs): + """ + ΠŸΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ save для автоматичСского вычислСния Ρ…Π΅ΡˆΠ° + """ + # ВычисляСм Ρ…Π΅Ρˆ ΠΏΠ΅Ρ€Π΅Π΄ сохранСниСм + if self.image: + self.image_hash = self.get_image_sha1_hash() + + super().save(*args, **kwargs) + + def update_hash(self): + """ + ΠžΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ‚ Ρ…Π΅Ρˆ изобраТСния (ΠΏΠΎΠ»Π΅Π·Π½ΠΎ Ссли Ρ„Π°ΠΉΠ» Π±Ρ‹Π» ΠΈΠ·ΠΌΠ΅Π½Π΅Π½) + """ + self.image_hash = self.get_image_sha1_hash() + self.save(update_fields=['image_hash']) + + @classmethod + def find_by_hash(cls, target_hash): + """ + Находит ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠΎ SHA1 Ρ…Π΅ΡˆΡƒ + + Args: + target_hash (str): SHA1 Ρ…Π΅Ρˆ для поиска + + Returns: + QuerySet: НайдСнныС изобраТСния + """ + return cls.objects.filter(image_hash=target_hash) + + @classmethod + def find_duplicates(cls): + """ + Находит Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Ρ‹ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + + Returns: + dict: Π‘Π»ΠΎΠ²Π°Ρ€ΡŒ Π³Π΄Π΅ ΠΊΠ»ΡŽΡ‡ - Ρ…Π΅Ρˆ, Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ - список ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² + """ + from django.db.models import Count + + # Находим Ρ…Π΅ΡˆΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²ΡΡ‚Ρ€Π΅Ρ‡Π°ΡŽΡ‚ΡΡ большС ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ€Π°Π·Π° + duplicate_hashes = cls.objects.values('image_hash').annotate( + count=Count('image_hash') + ).filter(count__gt=1, image_hash__isnull=False) + + duplicates = {} + for item in duplicate_hashes: + hash_value = item['image_hash'] + duplicates[hash_value] = cls.objects.filter(image_hash=hash_value) + + return duplicates + + +class ImageHashUtility: + """ + Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹ΠΉ класс для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ…Π΅ΡˆΠ°ΠΌΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + + @staticmethod + def calculate_file_hash(file_path): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + + Args: + file_path (str): ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ + + Returns: + str: SHA1 Ρ…Π΅Ρˆ ΠΈΠ»ΠΈ None ΠΏΡ€ΠΈ ошибкС + """ + try: + with open(file_path, 'rb') as f: + file_content = f.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ° Ρ„Π°ΠΉΠ»Π° {file_path}: {e}") + return None + + @staticmethod + def calculate_django_file_hash(django_file): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ Django Ρ„Π°ΠΉΠ»Π° (FileField/ImageField) + + Args: + django_file: ΠžΠ±ΡŠΠ΅ΠΊΡ‚ Django Ρ„Π°ΠΉΠ»Π° + + Returns: + str: SHA1 Ρ…Π΅Ρˆ ΠΈΠ»ΠΈ None ΠΏΡ€ΠΈ ошибкС + """ + if not django_file: + return None + + try: + with django_file.open('rb') as f: + file_content = f.read() + return hashlib.sha1(file_content).hexdigest() + except Exception as e: + print(f"Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ° Django Ρ„Π°ΠΉΠ»Π°: {e}") + return None + + @staticmethod + def compare_images_by_hash(image1, image2): + """ + Π‘Ρ€Π°Π²Π½ΠΈΠ²Π°Π΅Ρ‚ Π΄Π²Π° изобраТСния ΠΏΠΎ ΠΈΡ… Ρ…Π΅ΡˆΠ°ΠΌ + + Args: + image1: ΠŸΠ΅Ρ€Π²ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (ImageWithHash ΠΎΠ±ΡŠΠ΅ΠΊΡ‚) + image2: Π’Ρ‚ΠΎΡ€ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (ImageWithHash ΠΎΠ±ΡŠΠ΅ΠΊΡ‚) + + Returns: + bool: True Ссли изобраТСния ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅, False Ссли Ρ€Π°Π·Π½Ρ‹Π΅ + """ + if not image1.image_hash or not image2.image_hash: + return False + + return image1.image_hash == image2.image_hash \ No newline at end of file diff --git a/imagehash/settings.py b/imagehash/settings.py new file mode 100644 index 0000000..8a5df27 --- /dev/null +++ b/imagehash/settings.py @@ -0,0 +1,99 @@ +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-example-key-for-demo-purposes-only' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'imagehash', # НашС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'imagehash.wsgi.application' + +# Database +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +# Password validation +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +LANGUAGE_CODE = 'ru-ru' +TIME_ZONE = 'Europe/Moscow' +USE_I18N = True +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'staticfiles' + +# Media files +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + +# Default primary key field type +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Настройки для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ² +FILE_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB +DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB \ No newline at end of file diff --git a/imagehash/urls.py b/imagehash/urls.py new file mode 100644 index 0000000..0ebe443 --- /dev/null +++ b/imagehash/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('api/upload/', views.upload_image, name='upload_image'), + path('api/hash//', views.get_image_hash, name='get_image_hash'), + path('api/duplicates/', views.find_duplicates, name='find_duplicates'), + path('api/search/', views.search_by_hash, name='search_by_hash'), + path('api/calculate-hash/', views.calculate_hash_from_file, name='calculate_hash_from_file'), +] \ No newline at end of file diff --git a/imagehash/views.py b/imagehash/views.py new file mode 100644 index 0000000..1a27385 --- /dev/null +++ b/imagehash/views.py @@ -0,0 +1,160 @@ +from django.shortcuts import render, get_object_or_404 +from django.http import JsonResponse, HttpResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +from django.core.files.storage import default_storage +from django.core.files.base import ContentFile +import json +from .models import ImageWithHash, ImageHashUtility + + +def index(request): + """ + Главная страница с Ρ„ΠΎΡ€ΠΌΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ изобраТСния + """ + images = ImageWithHash.objects.all().order_by('-created_at') + return render(request, 'index.html', {'images': images}) + + +@csrf_exempt +@require_http_methods(["POST"]) +def upload_image(request): + """ + API endpoint для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ изобраТСния ΠΈ получСния Π΅Π³ΠΎ SHA1 Ρ…Π΅ΡˆΠ° + """ + try: + if 'image' not in request.FILES: + return JsonResponse({'error': 'Π€Π°ΠΉΠ» изобраТСния Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½'}, status=400) + + image_file = request.FILES['image'] + name = request.POST.get('name', image_file.name) + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ изобраТСния + image_obj = ImageWithHash(name=name, image=image_file) + image_obj.save() # Π₯Сш Π±ΡƒΠ΄Π΅Ρ‚ вычислСн автоматичСски Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ save() + + return JsonResponse({ + 'success': True, + 'id': image_obj.id, + 'name': image_obj.name, + 'image_url': image_obj.image.url, + 'sha1_hash': image_obj.image_hash, + 'message': 'Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ ΠΈ Ρ…Π΅Ρˆ вычислСн' + }) + + except Exception as e: + return JsonResponse({'error': f'Ошибка ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅: {str(e)}'}, status=500) + + +@require_http_methods(["GET"]) +def get_image_hash(request, image_id): + """ + API endpoint для получСния SHA1 Ρ…Π΅ΡˆΠ° изобраТСния ΠΏΠΎ ID + """ + try: + image_obj = get_object_or_404(ImageWithHash, id=image_id) + + # Если Ρ…Π΅Ρˆ Π΅Ρ‰Π΅ Π½Π΅ вычислСн, вычисляСм Π΅Π³ΠΎ + if not image_obj.image_hash: + image_obj.update_hash() + + return JsonResponse({ + 'success': True, + 'id': image_obj.id, + 'name': image_obj.name, + 'sha1_hash': image_obj.image_hash, + 'image_url': image_obj.image.url + }) + + except Exception as e: + return JsonResponse({'error': f'Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ Ρ…Π΅ΡˆΠ°: {str(e)}'}, status=500) + + +@require_http_methods(["GET"]) +def find_duplicates(request): + """ + API endpoint для поиска Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + """ + try: + duplicates = ImageWithHash.find_duplicates() + + result = [] + for hash_value, images in duplicates.items(): + result.append({ + 'hash': hash_value, + 'count': images.count(), + 'images': [ + { + 'id': img.id, + 'name': img.name, + 'image_url': img.image.url + } for img in images + ] + }) + + return JsonResponse({ + 'success': True, + 'duplicates': result, + 'total_duplicate_groups': len(result) + }) + + except Exception as e: + return JsonResponse({'error': f'Ошибка ΠΏΡ€ΠΈ поискС Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ²: {str(e)}'}, status=500) + + +@require_http_methods(["GET"]) +def search_by_hash(request): + """ + API endpoint для поиска ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΏΠΎ Ρ…Π΅ΡˆΡƒ + """ + try: + target_hash = request.GET.get('hash') + if not target_hash: + return JsonResponse({'error': 'ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ hash обязатСлСн'}, status=400) + + images = ImageWithHash.find_by_hash(target_hash) + + result = [] + for img in images: + result.append({ + 'id': img.id, + 'name': img.name, + 'image_url': img.image.url, + 'sha1_hash': img.image_hash + }) + + return JsonResponse({ + 'success': True, + 'images': result, + 'count': len(result) + }) + + except Exception as e: + return JsonResponse({'error': f'Ошибка ΠΏΡ€ΠΈ поискС: {str(e)}'}, status=500) + + +@csrf_exempt +@require_http_methods(["POST"]) +def calculate_hash_from_file(request): + """ + API endpoint для вычислСния Ρ…Π΅ΡˆΠ° ΠΈΠ· Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° Π±Π΅Π· сохранСния + """ + try: + if 'file' not in request.FILES: + return JsonResponse({'error': 'Π€Π°ΠΉΠ» Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½'}, status=400) + + file_obj = request.FILES['file'] + hash_value = ImageHashUtility.calculate_django_file_hash(file_obj) + + if hash_value: + return JsonResponse({ + 'success': True, + 'filename': file_obj.name, + 'size': file_obj.size, + 'sha1_hash': hash_value + }) + else: + return JsonResponse({'error': 'НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π²Ρ‹Ρ‡ΠΈΡΠ»ΠΈΡ‚ΡŒ Ρ…Π΅Ρˆ'}, status=500) + + except Exception as e: + return JsonResponse({'error': f'Ошибка ΠΏΡ€ΠΈ вычислСнии Ρ…Π΅ΡˆΠ°: {str(e)}'}, status=500) \ No newline at end of file diff --git a/imagehash/wsgi.py b/imagehash/wsgi.py new file mode 100644 index 0000000..59a4cc3 --- /dev/null +++ b/imagehash/wsgi.py @@ -0,0 +1,6 @@ +import os +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'imagehash.settings') + +application = get_wsgi_application() \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..3f61a84 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'imagehash.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/media/images/test_image.png b/media/images/test_image.png new file mode 100644 index 0000000..e69de29 diff --git a/media/images/test_image_Gi2LpIp.png b/media/images/test_image_Gi2LpIp.png new file mode 100644 index 0000000000000000000000000000000000000000..11426fd2439a5ceeda007d68112e82bce26db617 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryoCO|{#S9GG!XV7ZFl&wkP>{>h h#WAE}PI5v*0+7eTz<9TJiwuy(;OXk;vd$@?2>_OI5iS4# literal 0 HcmV?d00001 diff --git a/media/images/test_image_gqVnRcc.png b/media/images/test_image_gqVnRcc.png new file mode 100644 index 0000000000000000000000000000000000000000..11426fd2439a5ceeda007d68112e82bce26db617 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryoCO|{#S9GG!XV7ZFl&wkP>{>h h#WAE}PI5v*0+7eTz<9TJiwuy(;OXk;vd$@?2>_OI5iS4# literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1168f33 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django>=4.2.0 +Pillow>=10.0.0 \ No newline at end of file diff --git a/setup_django.py b/setup_django.py new file mode 100644 index 0000000..7ed2884 --- /dev/null +++ b/setup_django.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +""" +Π‘ΠΊΡ€ΠΈΠΏΡ‚ для настройки Django ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° ΠΈ выполнСния ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΉ +""" + +import os +import sys +import django +from django.core.management import execute_from_command_line + +def setup_django(): + """ + Настройка Django ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + """ + print("πŸ”§ Настройка Django ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°...") + + # УстанавливаСм ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ окруТСния + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'imagehash.settings') + + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ Django + django.setup() + + print("βœ… Django ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½") + + # ВыполняСм ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ + print("πŸ“¦ Π’Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΉ...") + try: + execute_from_command_line(['manage.py', 'makemigrations']) + execute_from_command_line(['manage.py', 'migrate']) + print("βœ… ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½Ρ‹ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ") + except Exception as e: + print(f"⚠️ Ошибка ΠΏΡ€ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΉ: {e}") + print("ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ:") + print("python manage.py makemigrations") + print("python manage.py migrate") + +if __name__ == "__main__": + setup_django() \ No newline at end of file diff --git a/simple_example.py b/simple_example.py new file mode 100644 index 0000000..79d1ebe --- /dev/null +++ b/simple_example.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +""" +ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ получСния SHA1 Ρ…Π΅ΡˆΠ° содСрТимого изобраТСния +Π±Π΅Π· использования Django (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ hashlib) +""" + +import hashlib +import os +from io import BytesIO + + +def calculate_file_hash(file_path): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ Ρ„Π°ΠΉΠ»Π° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + + Args: + file_path (str): ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ + + Returns: + str: SHA1 Ρ…Π΅Ρˆ Π² ΡˆΠ΅ΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚Π΅Ρ€ΠΈΡ‡Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ + """ + sha1_hash = hashlib.sha1() + + with open(file_path, 'rb') as f: + # Π§ΠΈΡ‚Π°Π΅ΠΌ Ρ„Π°ΠΉΠ» ΠΏΠΎ частям для экономии памяти + for chunk in iter(lambda: f.read(4096), b""): + sha1_hash.update(chunk) + + return sha1_hash.hexdigest() + + +def calculate_bytes_hash(data): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ ΠΈΠ· Π±Π°ΠΉΡ‚ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… + + Args: + data (bytes): Π‘Π°ΠΉΡ‚ΠΎΠ²Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ + + Returns: + str: SHA1 Ρ…Π΅Ρˆ Π² ΡˆΠ΅ΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚Π΅Ρ€ΠΈΡ‡Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ + """ + return hashlib.sha1(data).hexdigest() + + +def calculate_file_like_hash(file_like_object): + """ + ВычисляСт SHA1 Ρ…Π΅Ρˆ ΠΈΠ· Ρ„Π°ΠΉΠ»ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° + + Args: + file_like_object: ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠΌ read() (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, BytesIO) + + Returns: + str: SHA1 Ρ…Π΅Ρˆ Π² ΡˆΠ΅ΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚Π΅Ρ€ΠΈΡ‡Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ + """ + sha1_hash = hashlib.sha1() + + # ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ Π² Π½Π°Ρ‡Π°Π»ΠΎ Ρ„Π°ΠΉΠ»Π° + file_like_object.seek(0) + + # Π§ΠΈΡ‚Π°Π΅ΠΌ всС содСрТимоС + data = file_like_object.read() + sha1_hash.update(data) + + return sha1_hash.hexdigest() + + +def create_test_image_data(): + """ + Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ тСстовыС Π΄Π°Π½Π½Ρ‹Π΅ изобраТСния (1x1 пиксСль PNG) + """ + # ΠœΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ PNG Ρ„Π°ΠΉΠ» (1x1 пиксСль, ΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½Ρ‹ΠΉ) + return b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82' + + +def main(): + """ + ДСмонстрация Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… способов вычислСния SHA1 Ρ…Π΅ΡˆΠ° + """ + print("πŸ” ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ получСния SHA1 Ρ…Π΅ΡˆΠ° содСрТимого изобраТСния") + print("=" * 70) + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ тСстовыС Π΄Π°Π½Π½Ρ‹Π΅ изобраТСния + print("1. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ тСстовых Π΄Π°Π½Π½Ρ‹Ρ… изобраТСния...") + image_data = create_test_image_data() + print(f" βœ… Π Π°Π·ΠΌΠ΅Ρ€ Π΄Π°Π½Π½Ρ‹Ρ…: {len(image_data)} Π±Π°ΠΉΡ‚") + + # Бпособ 1: ВычислСниС Ρ…Π΅ΡˆΠ° ΠΈΠ· Π±Π°ΠΉΡ‚ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… + print("\n2. ВычислСниС Ρ…Π΅ΡˆΠ° ΠΈΠ· Π±Π°ΠΉΡ‚ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…...") + hash_from_bytes = calculate_bytes_hash(image_data) + print(f" βœ… SHA1 Ρ…Π΅Ρˆ: {hash_from_bytes}") + + # Бпособ 2: ВычислСниС Ρ…Π΅ΡˆΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° + print("\n3. ВычислСниС Ρ…Π΅ΡˆΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°...") + file_like = BytesIO(image_data) + hash_from_file_like = calculate_file_like_hash(file_like) + print(f" βœ… SHA1 Ρ…Π΅Ρˆ: {hash_from_file_like}") + print(f" βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚: {hash_from_bytes == hash_from_file_like}") + + # Бпособ 3: Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² Ρ„Π°ΠΉΠ» ΠΈ вычислСниС Ρ…Π΅ΡˆΠ° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ + print("\n4. Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² Ρ„Π°ΠΉΠ» ΠΈ вычислСниС Ρ…Π΅ΡˆΠ° ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ...") + test_file_path = "/tmp/test_image.png" + + with open(test_file_path, 'wb') as f: + f.write(image_data) + + hash_from_file = calculate_file_hash(test_file_path) + print(f" βœ… SHA1 Ρ…Π΅Ρˆ: {hash_from_file}") + print(f" βœ… Π₯Сши ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚: {hash_from_bytes == hash_from_file}") + + # ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° + os.remove(test_file_path) + print(" βœ… Π’Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» ΡƒΠ΄Π°Π»Π΅Π½") + + # ДСмонстрация с Ρ€Π°Π·Π½Ρ‹ΠΌΠΈ изобраТСниями + print("\n5. Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ Ρ€Π°Π·Π½Ρ‹Ρ… ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ...") + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π²Ρ‚ΠΎΡ€ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ (Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ Π΄Ρ€ΡƒΠ³ΠΎΠ΅) + image_data_2 = image_data + b'\x00' # ДобавляСм ΠΎΠ΄ΠΈΠ½ Π±Π°ΠΉΡ‚ + + hash_1 = calculate_bytes_hash(image_data) + hash_2 = calculate_bytes_hash(image_data_2) + + print(f" βœ… Π₯Сш изобраТСния 1: {hash_1}") + print(f" βœ… Π₯Сш изобраТСния 2: {hash_2}") + print(f" βœ… Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅: {hash_1 == hash_2}") + + # ДСмонстрация с ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΌΠΈ изобраТСниями + print("\n6. Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ… ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ...") + image_data_copy = image_data # Π’ΠΎΡ‚ ΠΆΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ + hash_copy = calculate_bytes_hash(image_data_copy) + + print(f" βœ… Π₯Сш ΠΎΡ€ΠΈΠ³ΠΈΠ½Π°Π»Π°: {hash_1}") + print(f" βœ… Π₯Сш ΠΊΠΎΠΏΠΈΠΈ: {hash_copy}") + print(f" βœ… Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅: {hash_1 == hash_copy}") + + print("\nβœ… ДСмонстрация Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ!") + print("\nπŸ“ ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹:") + print(" - SHA1 Ρ…Π΅Ρˆ вычисляСтся для содСрТимого Ρ„Π°ΠΉΠ»Π°, Π° Π½Π΅ для ΠΈΠΌΠ΅Π½ΠΈ") + print(" - ΠžΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ всСгда ΠΈΠΌΠ΅ΡŽΡ‚ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΉ Ρ…Π΅Ρˆ") + print(" - Π Π°Π·Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ (Π΄Π°ΠΆΠ΅ Π½Π° 1 Π±Π°ΠΉΡ‚) ΠΈΠΌΠ΅ΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ Ρ…Π΅ΡˆΠΈ") + print(" - Π₯Сш Π½Π΅ зависит ΠΎΡ‚ ΠΏΡƒΡ‚ΠΈ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΈΠ»ΠΈ Π΅Π³ΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ²") + print("\nπŸ”§ Для Django ImageField ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅:") + print(" - image_obj.get_image_sha1_hash() - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ…Π΅Ρˆ") + print(" - image_obj.image_hash - ΠΏΠΎΠ»Π΅ с Ρ…Π΅ΡˆΠ΅ΠΌ") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5a6b190 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,315 @@ + + + + + + Django ImageField SHA1 Hash Calculator + + + +
+

πŸ” Django ImageField SHA1 Hash Calculator

+ + +
+

πŸ“€ Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅

+
+ +
+ +
+ +
+
+
+ + +
+

πŸ” Поиск ΠΏΠΎ SHA1 Ρ…Π΅ΡˆΡƒ

+ + + +
+
+ + +
+

⚑ БыстроС вычислСниС Ρ…Π΅ΡˆΠ°

+ + +
+
+ + +

πŸ“· Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ изобраТСния

+
+ {% for image in images %} +
+ {{ image.name }} +
+

{{ image.name }}

+

SHA1:

+
{{ image.image_hash|default:"НС вычислСн" }}
+

Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {{ image.created_at|date:"d.m.Y H:i" }}

+
+
+ {% empty %} +

Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Ρ‹

+ {% endfor %} +
+
+ + + + \ No newline at end of file diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..c1497c1 --- /dev/null +++ b/urls.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('imagehash.urls')), +] + +# ΠžΠ±ΡΠ»ΡƒΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΌΠ΅Π΄ΠΈΠ° Ρ„Π°ΠΉΠ»ΠΎΠ² Π² Ρ€Π΅ΠΆΠΈΠΌΠ΅ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file