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 + + + + + + + π ΠΠΎΠΈΡΠΊ ΠΏΠΎ SHA1 Ρ Π΅ΡΡ + + ΠΠ°ΠΉΡΠΈ + ΠΠ°ΠΉΡΠΈ Π΄ΡΠ±Π»ΠΈΠΊΠ°ΡΡ + + + + + + β‘ ΠΡΡΡΡΠΎΠ΅ Π²ΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ Ρ Π΅ΡΠ° + + ΠΡΡΠΈΡΠ»ΠΈΡΡ Ρ Π΅Ρ + + + + + π· ΠΠ°Π³ΡΡΠΆΠ΅Π½Π½ΡΠ΅ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ + + {% for image in images %} + + + + {{ 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
SHA1:
ΠΠ°Π³ΡΡΠΆΠ΅Π½ΠΎ: {{ image.created_at|date:"d.m.Y H:i" }}
ΠΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ Π½Π΅ Π·Π°Π³ΡΡΠΆΠ΅Π½Ρ