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 0000000..9d132eb Binary files /dev/null and b/__pycache__/urls.cpython-313.pyc differ diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..ec9fd50 Binary files /dev/null and b/db.sqlite3 differ 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 0000000..27bad26 Binary files /dev/null and b/imagehash/__pycache__/__init__.cpython-313.pyc differ diff --git a/imagehash/__pycache__/admin.cpython-313.pyc b/imagehash/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..5ac3b9f Binary files /dev/null and b/imagehash/__pycache__/admin.cpython-313.pyc differ diff --git a/imagehash/__pycache__/apps.cpython-313.pyc b/imagehash/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..5c289f1 Binary files /dev/null and b/imagehash/__pycache__/apps.cpython-313.pyc differ diff --git a/imagehash/__pycache__/models.cpython-313.pyc b/imagehash/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..6552def Binary files /dev/null and b/imagehash/__pycache__/models.cpython-313.pyc differ diff --git a/imagehash/__pycache__/settings.cpython-313.pyc b/imagehash/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000..d8a6f90 Binary files /dev/null and b/imagehash/__pycache__/settings.cpython-313.pyc differ diff --git a/imagehash/__pycache__/urls.cpython-313.pyc b/imagehash/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000..c31497c Binary files /dev/null and b/imagehash/__pycache__/urls.cpython-313.pyc differ diff --git a/imagehash/__pycache__/views.cpython-313.pyc b/imagehash/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000..a607e62 Binary files /dev/null and b/imagehash/__pycache__/views.cpython-313.pyc differ 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 0000000..dcb8691 Binary files /dev/null and b/imagehash/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/imagehash/migrations/__pycache__/__init__.cpython-313.pyc b/imagehash/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..fe76754 Binary files /dev/null and b/imagehash/migrations/__pycache__/__init__.cpython-313.pyc differ 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 0000000..11426fd Binary files /dev/null and b/media/images/test_image_Gi2LpIp.png differ diff --git a/media/images/test_image_gqVnRcc.png b/media/images/test_image_gqVnRcc.png new file mode 100644 index 0000000..11426fd Binary files /dev/null and b/media/images/test_image_gqVnRcc.png differ 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