diff --git a/sandbox/conf/dev.py b/sandbox/conf/dev.py index ae21ae5..864e8d4 100644 --- a/sandbox/conf/dev.py +++ b/sandbox/conf/dev.py @@ -1,7 +1,7 @@ from .base import * # noqa INTERNAL_IPS = ["127.0.0.1"] - +ABSOLUTE_URL_BASE = "http://127.0.0.1:8000" DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", diff --git a/src/accounts/tests/factories/user_factory.py b/src/accounts/tests/factories/user_factory.py index f9a2129..9564240 100644 --- a/src/accounts/tests/factories/user_factory.py +++ b/src/accounts/tests/factories/user_factory.py @@ -8,19 +8,8 @@ faker = Faker() -# @factory.django.mute_signals(pre_save, post_save) -# class UserFactory(factory.django.DjangoModelFactory): -# username = factory.Sequence(lambda n: f"user-{n}") -# email = factory.LazyAttribute(lambda _: faker.unique.email()) -# password = factory.PostGenerationMethodCall("set_password", "12345abc") - -# class Meta: -# model = User -# django_get_or_create = ("username", "email") - - class UserFactory(factory.django.DjangoModelFactory): - username = factory.Sequence(lambda n: f"user-{n}") + username = factory.Sequence(lambda _: faker.unique.user_name()) email = factory.LazyAttribute(lambda _: faker.unique.email()) password = factory.PostGenerationMethodCall("set_password", "12345abc") @@ -30,7 +19,7 @@ class Meta: class AdminSupUserFactory(factory.django.DjangoModelFactory): - username = factory.Sequence(lambda n: f"user-{n}") + username = factory.Sequence(lambda _: faker.unique.user_name()) email = factory.LazyAttribute(lambda _: faker.unique.email()) password = factory.PostGenerationMethodCall("set_password", "12345abc") is_staff = True @@ -42,7 +31,7 @@ class Meta: class StaffUserFactory(factory.django.DjangoModelFactory): - username = factory.Sequence(lambda n: f"user-{n}") + username = factory.Sequence(lambda _: faker.unique.user_name()) email = factory.LazyAttribute(lambda _: faker.unique.email()) password = factory.PostGenerationMethodCall("set_password", "12345abc") is_staff = True diff --git a/src/contacts/admin.py b/src/contacts/admin.py new file mode 100644 index 0000000..7a15c1c --- /dev/null +++ b/src/contacts/admin.py @@ -0,0 +1,20 @@ +from django.contrib import admin + +from .models import NewsLetter + + +@admin.register(NewsLetter) +class NewsLetterAdmin(admin.ModelAdmin): + """ + after sending letter status as well as + related posts status should be changd + """ + + date_hierarchy = "added_at" + search_fields = ("title", "letter_status") + + list_display = ["id", "title", "letter_status"] + + list_filter = ["added_at", "letter_status"] + save_on_top = True + list_per_page = 15 diff --git a/src/contacts/jobs/__init__.py b/src/contacts/jobs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/contacts/jobs/send_news.py b/src/contacts/jobs/send_news.py new file mode 100644 index 0000000..6f55604 --- /dev/null +++ b/src/contacts/jobs/send_news.py @@ -0,0 +1,53 @@ +from django.conf import settings +from django.core import mail +from django.template.loader import render_to_string +from django.utils import timezone +from django_extensions.management.jobs import WeeklyJob + +from src.contacts.models import NewsLetter +from src.posts.models.post_model import Post +from src.profiles.models import Profile + + +class Job(WeeklyJob): + """ + users: active status and profile wanted_niews + will get email with news weekly; + let op: Job package; + """ + + help = "Send news letter" # noqa + + def execute(self): + _date = timezone.localdate() + str_date = _date.strftime("%d/%m/%Y") + stamp = f"Newsletter {_date:%A}, {_date:%b}. {_date:%d} {str_date}" + domain = settings.ABSOLUTE_URL_BASE + profiles = Profile.objects.send_news().select_related("user") + letter = NewsLetter.objects.filter(letter_status=1).last() + ctx = {"letter": letter} + posts = Post.objects.filter(send_status=1, letter=letter) + if letter.posts: + ctx.update({"posts": letter.posts.all(), "domain": domain}) + if letter and profiles: + text_msg = render_to_string("contacts/emails/letter.txt", ctx) + html_msg = render_to_string("contacts/emails/letter.html", ctx) + try: + for profile in profiles: + mail.send_mail( + subject=stamp, + message=text_msg, + html_message=html_msg, + from_email="From MedSandbox", + recipient_list=[profile.user.email], + ) + letter.sended_at = timezone.now() + letter.letter_status = 2 + letter.save() + for post in posts: + post.send_status = 2 + post.save() + + except Exception as e: + print(e) + # TODO: add Log diff --git a/src/contacts/migrations/0002_newsletter_sended_at.py b/src/contacts/migrations/0002_newsletter_sended_at.py new file mode 100644 index 0000000..6523d3d --- /dev/null +++ b/src/contacts/migrations/0002_newsletter_sended_at.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2023-07-28 18:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("contacts", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="newsletter", + name="sended_at", + field=models.DateField(blank=True, null=True), + ), + ] diff --git a/src/contacts/models.py b/src/contacts/models.py index ddb7586..55d225c 100644 --- a/src/contacts/models.py +++ b/src/contacts/models.py @@ -21,6 +21,7 @@ class Status(models.IntegerChoices): letter_status = models.IntegerField( choices=Status.choices, default=Status.PENDING, blank=True ) + sended_at = models.DateField(null=True, blank=True) def __str__(self) -> str: return f"sent news {self.id}" diff --git a/src/contacts/tests/__init__.py b/src/contacts/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/contacts/tests/factories.py b/src/contacts/tests/factories.py new file mode 100644 index 0000000..e18838b --- /dev/null +++ b/src/contacts/tests/factories.py @@ -0,0 +1,11 @@ +import factory + +from src.contacts.models import NewsLetter + + +class NewsLetterFactory(factory.django.DjangoModelFactory): + class Meta: + model = NewsLetter + + title = factory.Faker("word") + text = factory.Faker("sentence") diff --git a/src/contacts/tests/test_jobs.py b/src/contacts/tests/test_jobs.py new file mode 100644 index 0000000..51b2d97 --- /dev/null +++ b/src/contacts/tests/test_jobs.py @@ -0,0 +1,59 @@ +import time_machine + +from src.contacts.jobs.send_news import Job as SendMailJob +from src.contacts.models import NewsLetter +from src.posts.models.post_model import Post +from src.posts.tests.factories import PostFactory +from src.profiles.tests.factories.profile_factory import ProfileFactory + +from .factories import NewsLetterFactory + + +class TestSendEmailJob: + @time_machine.travel("2023-07-17 00:00 +0000") + def test_send_news(self, mailoutbox): + """ + active user (can) get news letter via email + with corresp links to posts; + if sending OK: letter and related posts + change their status + """ + subject = "Newsletter Monday, Jul. 17 17/07/2023" + profile = ProfileFactory(want_news=True) + post = PostFactory(send_status=1) + post_title = post.title + + letter = NewsLetterFactory(letter_status=1) + post.letter = letter + post.save() + # TODO: add a tag with link + # domain = settings.ABSOLUTE_URL_BASE + # link = f'", obj.top_img_url) - # return format_html("{}", url, func(self, related_obj)) - # if obj.top_img: - # return mark_safe(f"") def display_tags(self, obj): """if tags make a flat list of them""" @@ -98,6 +95,8 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): kwargs["queryset"] = get_user_model().objects.filter( username=request.user.username ) + if db_field.name == "letter": + kwargs["queryset"] = NewsLetter.objects.filter(letter_status=1) return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_readonly_fields(self, request, obj=None): @@ -106,12 +105,13 @@ def get_readonly_fields(self, request, obj=None): author field will be current user from request """ if obj is not None: - return self.readonly_fields + ("author",) + return self.readonly_fields + ("author", "letter") return self.readonly_fields def add_view(self, request, form_url="", extra_context=None): data = request.GET.copy() data["author"] = request.user + data["letter"] = NewsLetter.objects.filter(letter_status=1).last() request.GET = data return super().add_view(request, form_url="", extra_context=extra_context) diff --git a/src/templates/contacts/emails/letter.html b/src/templates/contacts/emails/letter.html new file mode 100644 index 0000000..469d68e --- /dev/null +++ b/src/templates/contacts/emails/letter.html @@ -0,0 +1,29 @@ +{% load i18n %} + + + + +
+

Greetings from MedSandbox

+

Here is some fresh content on our site

+ {% if letter %} +

Letter title: {{letter.title}}

+

Letter text: {{ letter.text|linebreaksbr }}

+

Here is a link to read a new article: + {% if posts %} + {% for post in posts %} + Post title: {{post.title}} + {% comment %} + Some post

+ {% endcomment %} + {% endfor%} + {% else %} +

No posts

+ {% endif %} + {% endif %} +

Best regards,

+

Admin

+ +
+ + diff --git a/src/templates/contacts/emails/letter.txt b/src/templates/contacts/emails/letter.txt new file mode 100644 index 0000000..dae390f --- /dev/null +++ b/src/templates/contacts/emails/letter.txt @@ -0,0 +1,11 @@ +{% load i18n %}Greetings from MedSandbox +Here is some fresh content on our site +{% if letter %} +Date {{ letter.added_at|date:"l, M. j, Y" }} +Letter title: {{letter.title}} +Letter text: {{ letter.text|linebreaksbr }} +{% if posts %}{% for post in posts %} +Post title: {{post.title}}{% endfor%} +{% endif %}{% endif %} +{% comment %} TODO: Link to unsubscribe: placeholder by now +{% endcomment %} \ No newline at end of file