-
Notifications
You must be signed in to change notification settings - Fork 0
fix&feat: replace logic, add crud for user #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import datetime | ||
| import requests | ||
| from bs4 import BeautifulSoup | ||
| from library.models import Book, Category | ||
|
|
||
| def scrape_book_info(url): | ||
| try: | ||
| resp = requests.get(url) | ||
| resp.raise_for_status() | ||
| except Exception as e: | ||
| raise Exception(f"Error querying URL: {str(e)}") | ||
|
|
||
| soup = BeautifulSoup(resp.text, "html.parser") | ||
| title_tag = soup.find("h1") | ||
| meta_description = soup.find("meta", attrs={"name": "description"}) | ||
| scraped_title = title_tag.text.strip() if title_tag else "Unknown" | ||
| scraped_description = ( | ||
| meta_description.get("content", "").strip() | ||
| if meta_description and meta_description.get("content") | ||
| else "" | ||
| ) | ||
|
|
||
| default_category, _ = Category.objects.get_or_create(name="Scraped") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Are you sure that you have only one category - "Scraped" ? As I understand you have to get category from response |
||
| book, created = Book.objects.update_or_create( | ||
| title=scraped_title, | ||
| category=default_category, | ||
| defaults={ | ||
| "author": "Unknown", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Check this hardcoded value. Mb u have to get it from response? |
||
| "publication_date": datetime.date.today(), | ||
| "description": scraped_description, | ||
| }, | ||
| ) | ||
| return book, created | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 5.1.5 on 2025-02-21 08:48 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("users", "0001_initial"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="user", | ||
| name="extra_fields", | ||
| field=models.JSONField(default=dict), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,6 @@ | |
|
|
||
|
|
||
| class UserManager(BaseUserManager): | ||
|
|
||
| def create_user(self, email, password=None, **extra_fields): | ||
| if not email: | ||
| raise ValueError("The Email field must be set") | ||
|
|
@@ -19,18 +18,14 @@ def create_superuser(self, email, password=None, **extra_fields): | |
| extra_fields.setdefault("is_staff", True) | ||
| extra_fields.setdefault("is_superuser", True) | ||
|
|
||
| if extra_fields.get("is_staff") is not True: | ||
| raise ValueError("Superuser must have is_staff=True.") | ||
| if extra_fields.get("is_superuser") is not True: | ||
| raise ValueError("Superuser must have is_superuser=True.") | ||
|
|
||
| return self.create_user(email, password, **extra_fields) | ||
|
|
||
|
|
||
| class User(AbstractUser): | ||
| email = models.EmailField(unique=True) | ||
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||
| username = models.CharField(max_length=15, unique=True) | ||
| extra_fields = models.JSONField(default=dict) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Why do you need this field? |
||
|
|
||
| USERNAME_FIELD = "username" | ||
| EMAIL_FIELD = "email" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,33 @@ | ||
| from django.conf import settings | ||
| from django.contrib.auth import get_user_model | ||
| from django.contrib.auth.tokens import default_token_generator | ||
| from django.core.mail import send_mail | ||
| from django.utils.encoding import force_bytes, force_str | ||
| from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode | ||
| from rest_framework import serializers | ||
| from users.models import User | ||
|
|
||
| User = get_user_model() | ||
|
|
||
|
|
||
| class UserSerializer(serializers.ModelSerializer): | ||
| class Meta: | ||
| model = User | ||
| fields = ["id", "username", "email"] | ||
| fields = ["id", "username", "email", "extra_fields", "is_staff", "is_superuser"] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Add validate method to check data for user updating method |
||
|
|
||
| def __init__(self, *args, **kwargs): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Instead of using |
||
| super().__init__(*args, **kwargs) | ||
| request = self.context.get("request") | ||
| if request and not request.user.is_superuser: | ||
| self.fields.pop("is_staff", None) | ||
| self.fields.pop("is_superuser", None) | ||
| self.fields.pop("extra_fields", None) | ||
|
|
||
| class PasswordResetRequestSerializer(serializers.Serializer): | ||
| email = serializers.EmailField() | ||
|
|
||
| def validate_email(self, value): | ||
| if not User.objects.filter(email=value).exists(): | ||
| raise serializers.ValidationError("User with this email does not exist") | ||
| return value | ||
|
|
||
|
|
||
| class PasswordResetConfirmSerializer(serializers.Serializer): | ||
| new_password = serializers.CharField(write_only=True) | ||
|
|
||
| def validate_new_password(self, value): | ||
| if len(value) < 6: | ||
| raise serializers.ValidationError("Password too short") | ||
| return value | ||
| def update(self, instance, validated_data): | ||
| instance.username = validated_data.get("username", instance.username) | ||
| instance.email = validated_data.get("email", instance.email) | ||
| instance.save() | ||
| return instance | ||
|
|
||
|
|
||
| class UserRegistrationSerializer(serializers.ModelSerializer): | ||
|
|
@@ -43,3 +47,49 @@ def create(self, validated_data): | |
| validated_data.pop("password2") | ||
| user = User.objects.create_user(**validated_data) | ||
| return user | ||
|
|
||
|
|
||
| class PasswordResetRequestSerializer(serializers.Serializer): | ||
| email = serializers.EmailField() | ||
|
|
||
| def validate_email(self, value): | ||
| if not User.objects.filter(email=value).exists(): | ||
| raise serializers.ValidationError("User with this email does not exist") | ||
| return value | ||
|
|
||
| def send_reset_email(self): | ||
| email = self.validated_data["email"] | ||
| user = User.objects.get(email=email) | ||
| token = default_token_generator.make_token(user) | ||
| uid = urlsafe_base64_encode(force_bytes(user.pk)) | ||
| reset_url = ( | ||
| f"{settings.URL_NGROK_HOST}/users/reset-password-confirm/{uid}/{token}/" | ||
| ) | ||
| send_mail( | ||
| subject="Password Reset Request", | ||
| message=f"Use the link to reset your password: {reset_url}", | ||
| from_email=None, | ||
| recipient_list=[email], | ||
| ) | ||
| return reset_url | ||
|
|
||
|
|
||
| class PasswordResetConfirmSerializer(serializers.Serializer): | ||
| new_password = serializers.CharField(write_only=True) | ||
|
|
||
| def validate(self, attrs): | ||
| uidb64 = self.context.get("uidb64") | ||
| token = self.context.get("token") | ||
| try: | ||
| uid = force_str(urlsafe_base64_decode(uidb64)) | ||
| self.user = User.objects.get(pk=uid) | ||
| except (TypeError, ValueError, User.DoesNotExist): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls Split these errors: User.DoesNotExist should return a 404 error when something is not found. |
||
| raise serializers.ValidationError("Invalid user") | ||
|
|
||
| if not default_token_generator.check_token(self.user, token): | ||
| raise serializers.ValidationError("Invalid or expired token") | ||
| return attrs | ||
|
|
||
| def save(self): | ||
| self.user.set_password(self.validated_data["new_password"]) | ||
| self.user.save() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ | |
| PasswordResetConfirmView, | ||
| PasswordResetRequestView, | ||
| UserRegistrationView, | ||
| UserRetrieveUpdateView, | ||
| UserRetrieveView, | ||
| UserViewSet, | ||
| ) | ||
|
|
||
| urlpatterns = [ | ||
|
|
@@ -16,5 +16,15 @@ | |
| name="reset-password-confirm", | ||
| ), | ||
| path("register/", UserRegistrationView.as_view(), name="user-register"), | ||
| path("profile/<uuid:id>/", UserRetrieveUpdateView.as_view(), name="user-retrieve"), | ||
| path( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Sn1kls I think that you can use router here instead of use one path for 3 different methods |
||
| "<uuid:pk>/", | ||
| UserViewSet.as_view( | ||
| { | ||
| "patch": "partial_update", | ||
| "put": "update", | ||
| "delete": "destroy", | ||
| } | ||
| ), | ||
| name="user-detail", | ||
| ), | ||
| ] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Sn1kls Try moving validation to the validate_url or validate method. Also, instead of returning a generic 'Error scraping book,' try to provide a more specific and understandable error message (e.g., 'Book not found,' 'Invalid site URL,' etc.). If possible, the create method should operate only with already validated data, as long as it does not affect system optimization.