Skip to content

Conversation

@rasulkireev
Copy link
Owner

@rasulkireev rasulkireev commented Aug 9, 2025

Summary by CodeRabbit

  • New Features

    • Introduced automatic blog post submission, including a UI toggle in project settings to enable or disable this feature.
    • Added tracking of when a generated blog post is posted.
    • Added new project and profile settings for experimental features and automatic post generation.
    • Implemented background scheduling for automatic post generation and submission.
    • Added a detailed onboarding and architecture guide to the documentation.
  • Improvements

    • Enhanced code style and rule documentation for backend error handling and logging.
    • Updated type hinting for improved Python 3.11 compatibility.
    • Upgraded multiple dependencies for better compatibility and stability.
    • Added a changelog following standard conventions.
  • Bug Fixes

    • Improved error handling and logging in API endpoints.
    • Ensured accurate timestamping when marking blog posts as posted.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@rasulkireev has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 643bd90 and ce176fc.

📒 Files selected for processing (5)
  • core/forms.py (2 hunks)
  • core/tasks.py (4 hunks)
  • core/views.py (1 hunks)
  • frontend/templates/components/landing_page.html (1 hunks)
  • frontend/templates/project/project_settings.html (3 hunks)

Walkthrough

This update introduces an automated blog post submission feature. It adds new Boolean fields to models, updates database migrations, and implements backend logic for scheduling and toggling automatic submissions. A new API endpoint and frontend toggle UI are provided. Documentation, code style rules, and dependencies are also updated.

Changes

Cohort / File(s) Change Summary
Automated Post Submission: Models & Migration
core/models.py, core/migrations/0028_generatedblogpost_date_posted_and_more.py
Added fields to Profile and Project models for experimental features and auto-submission; added date_posted to GeneratedBlogPost; migration created for these changes; added properties to Project model.
Automated Post Submission: API & Logic
core/api/views.py, core/api/schemas.py, core/tasks.py
Introduced API endpoint for toggling auto-submission; added new schema for toggle output; implemented scheduling and posting logic for automated blog posts; updated type hints and formatting.
Frontend: Auto-Submission Toggle
frontend/src/controllers/auto_submission_toggle_controller.js, frontend/templates/project/project_settings.html
Added Stimulus controller and UI toggle for enabling/disabling automatic post submission in project settings.
Documentation & Changelog
CLAUDE.md, CHANGELOG.md
Replaced minimal documentation with a comprehensive guide; added a changelog file following Keep a Changelog standards.
Linter & Code Style Rules
.cursor/rules/backend-error-handling.mdc, .cursor/rules/backend-logging.mdc, .cursor/rules/code-style.mdc, pyproject.toml
Updated code style and backend rules, restricted scope to Python files, modified enforcement flags; added Ruff ignore for complexity in models.py.
Dependency Upgrades
requirements.txt
Upgraded many dependencies for Python 3.11 compatibility and added new packages.
Miscellaneous
.gitignore
Removed ignore rules for tasks.json and tasks/.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant FrontendController as AutoSubmissionToggleController.js
    participant API as Django API
    participant DB as Database

    User->>Browser: Clicks auto-submission toggle
    Browser->>FrontendController: Handles toggle event
    FrontendController->>API: POST /projects/{project_id}/toggle-auto-submission
    API->>DB: Toggle enable_automatic_post_submission for Project
    DB-->>API: Updated Project state
    API-->>FrontendController: Response with new enabled state
    FrontendController->>Browser: Updates toggle UI
Loading
sequenceDiagram
    participant Scheduler as schedule_blog_post_posting (task)
    participant DB as Database
    participant API as Django API
    participant BlogPostTask as generate_and_post_blog_post

    Scheduler->>DB: Query Projects with auto-submission enabled
    loop For each eligible Project
        Scheduler->>BlogPostTask: Trigger generate_and_post_blog_post(project_id)
        BlogPostTask->>DB: Fetch unposted/generated blog posts or generate new
        BlogPostTask->>API: Submit blog post via endpoint
        API->>DB: Mark as posted, set date_posted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

A toggle appears, a switch to the skies,
Now posts can fly out with no need for replies!
Models get fields, migrations take note,
Tasks hum along, as blog posts are wrote.
With docs refreshed and changelog anew,
This rabbit’s quite proud of what auto can do!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-post-submission

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Summary

This PR implements an automatic blog post submission feature that allows projects to automatically post generated content to external endpoints. The implementation adds two key controls to the Project model: enable_automatic_post_generation (defaulting to True for backward compatibility) and enable_automatic_post_submission (defaulting to False for safety).

The feature introduces a new API endpoint /projects/{project_id}/toggle-auto-submission that allows users to enable/disable automatic submission, paired with a Stimulus controller that provides an intuitive toggle UI with optimistic updates. The GeneratedBlogPost model gains a date_posted field to track when posts were actually submitted, complementing the existing posted boolean field.

The codebase also receives significant modernization improvements, including migration to Python 3.10+ union type syntax (str | None instead of Optional[str]), extensive code formatting standardization following ruff/Black standards, and refinement of Cursor IDE rules to be more targeted and less intrusive. Additionally, the CLAUDE.md file is completely rewritten from a simple command reference into a comprehensive 128-line architectural guide for AI-assisted development.

The auto-submission workflow builds upon the existing AutoSubmissionSetting model and integrates with the project's django-q2 task system, though the actual scheduling logic in schedule_blog_post_posting() remains incomplete. The feature maintains a clear separation between content generation and submission, allowing users granular control over their automation preferences.

Confidence score: 2/5

  • This PR introduces significant new functionality but has incomplete implementation that could cause runtime issues
  • Score lowered due to incomplete schedule_blog_post_posting() function and missing CSRF protection in the new Stimulus controller
  • Pay close attention to core/tasks.py (incomplete function), frontend/src/controllers/auto_submission_toggle_controller.js (missing CSRF token), and the overall integration between the new toggle endpoint and the incomplete scheduling system

15 files reviewed, 5 comments

Edit Code Review Bot Settings | Greptile

Comment on lines 286 to 298
<button
type="button"
data-action="auto-submission-toggle#toggle"
data-auto-submission-toggle-target="toggle"
class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
role="switch"
aria-checked="{{ project.enable_automatic_post_submission|lower }}"
>
<span
data-auto-submission-toggle-target="switch"
class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform"
></span>
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Button indentation is inconsistent - lines should align with the opening tag


@property
def generated_blog_posts(self):
return self.generated_blog_posts.all()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Property name conflicts with related_name. The model has related_name='generated_blog_posts' but this property has the same name, which could cause confusion.

Comment on lines +19 to +24
fetch(`/api/projects/${this.projectIdValue}/toggle-auto-submission`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Missing CSRF token in headers. Other controllers in this codebase (like project_details_controller.js) include "X-CSRFToken" header for POST requests

Suggested change
fetch(`/api/projects/${this.projectIdValue}/toggle-auto-submission`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
fetch(`/api/projects/${this.projectIdValue}/toggle-auto-submission`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value,
},
})

// Revert optimistic update on failure
this.enabledValue = !this.enabledValue;
this.updateToggleState();
console.error("Error toggling auto-submission:", error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using a more user-friendly error message instead of logging to console, similar to how project_details_controller.js uses showMessage() utility

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🔭 Outside diff range comments (5)
.cursor/rules/code-style.mdc (1)

2-4: Frontmatter incomplete; add description and concrete globs, plus actionable examples.

Per our Cursor rules standards, description must be a clear one-liner and globs must specify targets. Also include DO/DON'T examples for the “double quotes” rule and cross-reference related rules to keep things DRY.

Proposed update:

 ---
-description:
-globs:
-alwaysApply: true
+description: Enforce consistent code style for Python with simple, clear conventions.
+globs: "*.py"
+alwaysApply: true
 ---
 
-- Use double quotes instead of single quotes
+- Use double quotes instead of single quotes
+
+DO (Python):
+```python
+name = "Alice"
+logger.info("Created user", name=name)
+```
+
+DON'T:
+```python
+name = 'Alice'
+logger.info('Created user', name=name)
+```
+
+See also: [backend-logging.mdc](mdc:.cursor/rules/backend-logging.mdc) for logging-related conventions.

Also applies to: 6-7

core/tasks.py (1)

264-271: Implement minimal scheduler logic and avoid N+1.

  • Filter projects that already have auto-submission settings to skip per-row .exists() calls.
  • Guard against last_posted_blog_post being None.
  • Add basic logging/return to match other task functions.

Apply:

-def schedule_blog_post_posting():
-    projects = Project.objects.filter(enable_automatic_post_submission=True)
-    for project in projects:
-        if not project.has_auto_submission_setting:
-            continue
-
-        # last_post_date = project.last_posted_blog_post.date_posted
+def schedule_blog_post_posting():
+    projects = (
+        Project.objects.filter(
+            enable_automatic_post_submission=True,
+            auto_submission_settings__isnull=False,
+        )
+        .distinct()
+    )
+    scheduled = 0
+    for project in projects:
+        last_post = project.last_posted_blog_post
+        last_post_date = last_post.date_posted if last_post else None
+        # TODO: compute if it's time to post based on project's auto-submission settings
+        # Example: async_task(post_next_generated_blog_post, project.id)
+        # scheduled += 1 when a job is queued
+        logger.info(
+            "[AutoSubmissionScheduler] Scanned project",
+            project_id=project.id,
+            last_post_date=str(last_post_date) if last_post_date else None,
+        )
+    logger.info(
+        "[AutoSubmissionScheduler] Completed run",
+        scanned=projects.count(),
+        scheduled=scheduled,
+    )
+    return f"Auto-submission scheduler: scanned={projects.count()}, scheduled={scheduled}"

Also consider idempotency if this task can run concurrently across workers/nodes (e.g., mark the selected post as “scheduled” before enqueue).

core/api/schemas.py (3)

49-51: Mutable default in Schema field

target_keywords defaults to a shared list, which is unsafe. Use default_factory.

Apply:

+from pydantic import Field
@@
 class TitleSuggestionOut(Schema):
     id: int
     title: str
     category: str = ""
-    target_keywords: list[str] = []
+    target_keywords: list[str] = Field(default_factory=list)
     suggested_meta_description: str = ""

53-57: Invalid default for typed model (dict for TitleSuggestionOut)

suggestion has type TitleSuggestionOut but default {}. Make it optional or supply a valid instance.

Apply:

-class GenerateTitleSuggestionOut(Schema):
-    status: str
-    message: str = ""
-    suggestion: TitleSuggestionOut = {}
+class GenerateTitleSuggestionOut(Schema):
+    status: str
+    message: str = ""
+    suggestion: TitleSuggestionOut | None = None

If you need a non-null empty payload, define a minimal valid TitleSuggestionOut instead.


59-63: Mutable default list in response

Use default_factory for suggestions to avoid shared state.

Apply:

-class GenerateTitleSuggestionsOut(Schema):
-    suggestions: list[TitleSuggestionOut] = []
+class GenerateTitleSuggestionsOut(Schema):
+    suggestions: list[TitleSuggestionOut] = Field(default_factory=list)
     status: str
     message: str = ""

(Note: ensure Field is imported as shown earlier.)

🧹 Nitpick comments (12)
core/migrations/0028_project_enable_automatic_post_generation_and_more.py (1)

15-17: Confirm the default=True won’t change production behavior.

Defaulting enable_automatic_post_generation to True flips this on for all existing projects upon migration unless guarded in code. Validate this is intentional; otherwise set default=False or backfill selectively in a data migration.

I can draft a follow-up data migration to backfill existing rows safely if needed.

frontend/src/controllers/auto_submission_toggle_controller.js (2)

14-18: Preserve previous state before optimistic flip

this.enabledValue = !this.enabledValue loses the original value, making it impossible to guarantee a correct rollback in the catch-block if the request fails mid-flight.
Store the prior state first or revert using that snapshot rather than a second negation.

toggle() {
-  // Optimistically update UI
-  this.enabledValue = !this.enabledValue;
+  const previous = this.enabledValue;
+  this.enabledValue = !previous;   // optimistic
   this.updateToggleState();
   …
   .catch(() => {
-    this.enabledValue = !this.enabledValue; // might be wrong if state drifted
+    this.enabledValue = previous;           // reliable rollback
     this.updateToggleState();
   });
}

36-41: Surface fetch errors to the user, not just the console

A silent failure leaves users unaware that toggling didn’t persist. Dispatch a Stimulus event or show a toast/alert tied to the controller so UX remains clear.
Example:

.catch((error) => {
  this.enabledValue = previous;
  this.updateToggleState();
  this.dispatch("error", { detail: error });
});
core/api/views.py (1)

261-270: Add logging & idempotent handling to /toggle-auto-submission

  1. No log entry is recorded, which impairs auditability.
  2. A blind boolean flip can race under concurrent requests. Prefer an explicit “set” based on client intent (enabled: true/false) or wrap the update in select_for_update() to guarantee atomicity.
logger.info(
    "Toggling auto-submission",
    project_id=project_id,
    profile_id=profile.id,
    from_value=project.enable_automatic_post_submission,
)

Consider accepting a payload { "enabled": true } to make the operation idempotent.

CLAUDE.md (4)

35-44: Align tech stack notes with project conventions and cross-link docs

Good snapshot. Given our convention to minimize dependencies and favor Django built-ins, add a brief note here linking to project-conventions and stating that new deps require justification.


27-31: Add static type checking to Code Quality commands

We enforce type hints; add mypy/pyright step to CI/dev commands.

Proposed additions:

- - `poetry run ruff check` - Run Python linting
+ - `poetry run ruff check` - Run Python linting
+ - `poetry run mypy .` - Static type checking (or `pyright` if preferred)

Optionally document a pre-commit hook running ruff, mypy, djlint, pytest -q.


97-103: Specify runtime versions and dev-only superuser behavior

  • State required versions (Python, Node, npm, Poetry, Docker/Compose) to avoid subtle issues.
  • “First user becomes admin/superuser automatically” should be dev-only. Call that out explicitly to prevent production misconfiguration.

124-129: Deployment: add steps for migrate/collectstatic/health checks

Document typical deploy sequence: apply migrations, collectstatic, restart app and workers, and note health checks and worker queue readiness (Redis).

core/api/schemas.py (4)

130-141: Consider precise types for money and timestamps

  • cpc_value: float may introduce rounding errors. Prefer Decimal in schema as well, or a string-decimal to preserve precision.
  • last_fetched_at: use datetime | None and let Ninja serialize to ISO-8601 automatically.

Example:

-from decimal import Decimal
+from decimal import Decimal
+from datetime import datetime
@@
-    cpc_value: float | None = None  # Using float for schema, Decimal in model
+    cpc_value: Decimal | None = None
@@
-    last_fetched_at: str | None = None  # datetime converted to str
+    last_fetched_at: datetime | None = None

If API consumers require strings, you can still serialize via custom encoder.


160-169: Optional validation refinements (slug/tags)

If helpful, enforce slug format via Field regex; otherwise current schema is fine. Example:

-from pydantic import Field
+from pydantic import Field, constr
@@
-    slug: str
+    slug: constr(regex=r'^[a-z0-9]+(?:-[a-z0-9]+)*$')

Keeping icon/image as str is appropriate since you accept URL or base64.


181-184: Clean up stray changed lines

There appear to be no-op changes/blank lines; consider removing them to reduce noise in diffs.


185-189: Constrain status values for consistency

Many responses use status/message. Consider Literal values for status to constrain to known states.

-from typing import Literal
+from typing import Literal
@@
-    status: str
+    status: Literal["success", "error"]

Do similarly for other Out models that expose status.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bcce90 and eb35ec5.

📒 Files selected for processing (14)
  • .cursor/rules/backend-error-handling.mdc (1 hunks)
  • .cursor/rules/backend-logging.mdc (1 hunks)
  • .cursor/rules/code-style.mdc (1 hunks)
  • .gitignore (0 hunks)
  • CLAUDE.md (1 hunks)
  • core/api/schemas.py (6 hunks)
  • core/api/views.py (18 hunks)
  • core/migrations/0028_project_enable_automatic_post_generation_and_more.py (1 hunks)
  • core/migrations/0029_generatedblogpost_date_posted.py (1 hunks)
  • core/models.py (5 hunks)
  • core/tasks.py (2 hunks)
  • frontend/src/controllers/auto_submission_toggle_controller.js (1 hunks)
  • frontend/templates/project/project_settings.html (1 hunks)
  • pyproject.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • .gitignore
🧰 Additional context used
🧠 Learnings (40)
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: Cursor rule files (.mdc) must begin with a YAML frontmatter that includes a clear, one-line 'description', a 'globs' field specifying the file patterns the rule applies to, and an 'alwaysApply' boolean.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: Cursor rules should be regularly updated to reflect new patterns, include examples from the actual codebase, remove outdated patterns, and cross-reference related rules for maintainability.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: Best practices for writing Cursor rules include using bullet points for clarity, keeping descriptions concise, including both DO and DON'T examples, referencing actual code over theoretical examples, and maintaining consistent formatting across all rules.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: To maintain DRY principles, Cursor rules should reference other rules where appropriate instead of duplicating content.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: When referencing files within Cursor rules, use the format [filename](mdc:path/to/file) for rule files and [filename](mdc:relative/path) for code files to ensure clarity and navigability.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: Each Cursor rule should start with a high-level overview, provide specific actionable requirements, and include examples of correct implementation, referencing existing code when possible.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
📚 Learning: 2025-06-23T13:07:50.521Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/cursor_rules.mdc:0-0
Timestamp: 2025-06-23T13:07:50.521Z
Learning: Code examples in Cursor rules should use language-specific code blocks and clearly distinguish between good (DO) and bad (DON'T) patterns using comments and visual markers.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
📚 Learning: 2025-06-23T13:08:16.409Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-06-23T13:08:16.409Z
Learning: Update rules after major refactors and cross-reference related rules to ensure comprehensive and up-to-date guidance.

Applied to files:

  • .cursor/rules/code-style.mdc
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
  • CLAUDE.md
📚 Learning: 2025-07-05T14:18:33.306Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/code-style.mdc:0-0
Timestamp: 2025-07-05T14:18:33.306Z
Learning: Use double quotes instead of single quotes

Applied to files:

  • .cursor/rules/code-style.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/*.py : Lint Python code with ruff

Applied to files:

  • pyproject.toml
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/*.py : Format Python code using black

Applied to files:

  • pyproject.toml
  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/{models,views}.py : Follow Django conventions for models and views

Applied to files:

  • pyproject.toml
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/templates/**/*.html : Lint Django templates using djlint with profile=django

Applied to files:

  • pyproject.toml
📚 Learning: 2025-06-23T13:08:16.409Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-06-23T13:08:16.409Z
Learning: Deprecate outdated rules and document migration paths for old patterns to maintain clarity and prevent confusion.

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-06-23T13:08:16.409Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-06-23T13:08:16.409Z
Learning: Keep documentation and rule examples synchronized with the actual codebase, and update references to external documentation as needed.

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:58.980Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend-error-handling.mdc:0-0
Timestamp: 2025-07-05T14:17:58.980Z
Learning: Applies to **/*.py : Use try-except blocks for specific errors, try to avoid excepting Exception

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/*.py : Handle errors in Python with proper exception catching and logging

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:18:07.588Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend-logging.mdc:0-0
Timestamp: 2025-07-05T14:18:07.588Z
Learning: Applies to **/*.py : Use appropriate log levels (INFO, WARNING, ERROR)

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Applies to **/*.py : Prioritize readability and maintainability; follow PEP 8

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/*.py : Use type hints in Python code

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • core/api/schemas.py
📚 Learning: 2025-07-05T14:18:07.588Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend-logging.mdc:0-0
Timestamp: 2025-07-05T14:18:07.588Z
Learning: Applies to **/*.py : Use Python's logging module (structlog) extensively

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:18:07.588Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend-logging.mdc:0-0
Timestamp: 2025-07-05T14:18:07.588Z
Learning: Applies to **/*.py : Implement comprehensive logging

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Applies to **/*.py : Use descriptive variable/function names with underscores

Applied to files:

  • .cursor/rules/backend-error-handling.mdc
📚 Learning: 2025-07-05T14:18:07.588Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend-logging.mdc:0-0
Timestamp: 2025-07-05T14:18:07.588Z
Learning: Applies to **/*.py : Include context in log messages (e.g., logger.warning("Error occurred during profile update", error=str(e), profile_id=profile.id))

Applied to files:

  • .cursor/rules/backend-logging.mdc
  • core/api/views.py
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/*.py : Sort Python imports using isort with Django profile, combine_as_imports, and trailing_comma options

Applied to files:

  • .cursor/rules/backend-logging.mdc
📚 Learning: 2025-07-05T14:17:56.055Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-05T14:17:56.055Z
Learning: Applies to **/controllers/**/*.js : Use StimulusJS controllers for all interactive frontend elements

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-06-23T13:08:05.905Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/frontend.mdc:0-0
Timestamp: 2025-06-23T13:08:05.905Z
Learning: Use Stimulus controllers to encapsulate JavaScript behavior, ensuring a clear separation between JavaScript logic and HTML structure.

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-06-23T13:08:05.905Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/frontend.mdc:0-0
Timestamp: 2025-06-23T13:08:05.905Z
Learning: Utilize Stimulus targets to reference and manipulate specific elements within a controller, enabling organized and modular code.

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-06-23T13:08:05.905Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/frontend.mdc:0-0
Timestamp: 2025-06-23T13:08:05.905Z
Learning: Employ Stimulus actions to handle user interactions and events, following the Stimulus convention for event-driven behavior.

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-06-23T13:08:12.253Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-06-23T13:08:12.253Z
Learning: Favor semantic, native HTML features (such as `dialog`, `summary`/`details`, and the `popover` attribute) over JavaScript-based solutions, and leverage Stimulus JS to enhance these native elements.

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-06-23T13:08:23.655Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/stimulus-events.mdc:0-0
Timestamp: 2025-06-23T13:08:23.655Z
Learning: Adopting an event-based communication pattern in Stimulus leads to cleaner HTML markup by eliminating the need for data-*-outlet attributes and making each controller more self-contained and reusable.

Applied to files:

  • frontend/src/controllers/auto_submission_toggle_controller.js
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Use django-ninja for APIs

Applied to files:

  • core/api/views.py
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Applies to core/views.py : Use django-ninja's authentication classes

Applied to files:

  • core/api/views.py
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Use django-ninja instead of DRF

Applied to files:

  • core/api/views.py
📚 Learning: 2025-06-23T13:57:13.615Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/django-async-tasks.mdc:0-0
Timestamp: 2025-06-23T13:57:13.615Z
Learning: In Django projects using Django-Q for async tasks, never pass the HttpRequest object to a background task because it is not serializable and will cause pickling errors. Instead, extract only the specific, serializable data needed (such as user ID, cookies, session data, GET/POST parameters, or headers) and pass those to the task.

Applied to files:

  • core/api/views.py
📚 Learning: 2025-06-23T13:08:12.253Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-06-23T13:08:12.253Z
Learning: When working in a Django codebase, minimize dependencies and push Django to its limits before adding new ones. Only add a new dependency if there is a strong technical or business reason, and prefer old and reliable packages over new and flashy ones.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-06-23T13:08:12.253Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-06-23T13:08:12.253Z
Learning: Prefer server-side solutions using Django templates over complex JavaScript-driven client-side solutions. When client-side code is necessary, use Stimulus controllers and keep the implementation simple.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Use Django's built-in features and tools wherever possible

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: We will only have one django app, called 'core'. Everything about our webapp will be here

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-06-23T13:08:12.253Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-06-23T13:08:12.253Z
Learning: Follow the 'skinny views, fat models' convention in Django: organize business logic into Django models and mixins, and avoid creating separate service layers or folders for business logic.

Applied to files:

  • CLAUDE.md
🧬 Code Graph Analysis (3)
core/migrations/0029_generatedblogpost_date_posted.py (1)
core/migrations/0028_project_enable_automatic_post_generation_and_more.py (1)
  • Migration (6-23)
core/migrations/0028_project_enable_automatic_post_generation_and_more.py (1)
core/migrations/0029_generatedblogpost_date_posted.py (1)
  • Migration (6-18)
core/tasks.py (1)
core/models.py (2)
  • Project (185-639)
  • has_auto_submission_setting (271-272)
🔇 Additional comments (5)
frontend/templates/project/project_settings.html (1)

303-303: LGTM on spacing change.

Removing the extra top margin from the form is fine and keeps vertical rhythm tighter under the new toggle.

CLAUDE.md (3)

91-96: Tailwind + Bootstrap coexistence: clarify CSS load order and scope

Using both can cause specificity/override issues. Document the CSS order (e.g., Bootstrap first, then Tailwind), whether Reboot is enabled, and any scoping strategy for Bootstrap components.

Please confirm the asset order in your bundler config and that this is intentional.


70-78: Document new Project fields enabling automation

Since this PR adds auto-generation/submission toggles and date_posted, list these on Project (enable_automatic_post_generation, enable_automatic_post_submission, date_posted) for discoverability.


22-26: Confirmed: frontend npm scripts are defined
The build, start, and watch commands documented in CLAUDE.md match the definitions in package.json. No changes required.

core/api/schemas.py (1)

65-73: Union syntax update looks good

Switch from Optional[…] to | None is correct for Python 3.10+ and compatible with pydantic validation.

Comment on lines +2 to +5
globs: *.py
alwaysApply: false
---
- Use try-except blocks for specific errors, try to avoid excepting Exception
- Use try-except blocks for specific errors, avoid excepting Exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Add required description, refine guidance, and include DO/DON'T examples.

Frontmatter is missing description. The bullet should include concrete examples and avoid ambiguous phrasing.

Proposed update:

 ---
-globs: *.py
-alwaysApply: false
+description: Handle Python errors with precise exception types and clear recovery/logging paths.
+globs: "*.py"
+alwaysApply: false
 ---
-- Use try-except blocks for specific errors, avoid excepting Exception
+- Use try/except blocks for specific exceptions; avoid catching broad exceptions (e.g., Exception, BaseException).
+
+DO:
+```python
+try:
+    user = repo.get_user(user_id)
+except NotFoundError as e:
+    logger.warning("User not found", user_id=user_id, error=str(e))
+    return None
+except RepoTimeout as e:
+    logger.error("User repo timeout", user_id=user_id, error=str(e))
+    raise
+```
+
+DON'T:
+```python
+try:
+    user = repo.get_user(user_id)
+except Exception as e:
+    logger.warning("Failed", error=str(e))  # Swallows important failures
+```
+
+See also:
+- [backend-logging.mdc](mdc:.cursor/rules/backend-logging.mdc) for logging guidance.
+- [backend.mdc](mdc:.cursor/rules/backend.mdc) for general backend best practices.

Confirmation requested:

  • Setting alwaysApply to false reduces automated enforcement. Is this intentional for team workflow?

Refine backend-error-handling.mdc: add description, examples, and clarify enforcement

The rule’s frontmatter needs a description field, clearer guidance, and concrete DO/DON’T examples.

• Add a one-line description above globs
• Expand the single bullet into precise wording with examples
• Reference related rules for logging and general backend best practices

Proposed diff:

 ---
-description: Handle Python errors with precise exception types and clear recovery/logging paths.
 globs: *.py
 alwaysApply: false
---
- Use try-except blocks for specific errors, avoid excepting Exception
+ Use try/except blocks for specific exceptions; avoid catching broad exceptions (e.g., Exception, BaseException).
+
+DO:
+```python
+try:
+    user = repo.get_user(user_id)
+except NotFoundError as e:
+    logger.warning("User not found", user_id=user_id, error=str(e))
+    return None
+except RepoTimeout as e:
+    logger.error("User repo timeout", user_id=user_id, error=str(e))
+    raise
+```
+
+DON’T:
+```python
+try:
+    user = repo.get_user(user_id)
+except Exception as e:
+    logger.warning("Failed", error=str(e))  # Swallows important failures
+```
+
+See also:
+- [.cursor/rules/backend-logging.mdc] for logging guidance  
+- [.cursor/rules/backend.mdc] for general backend best practices  

Confirmation:

  • Is setting alwaysApply: false intentional to allow manual adoption?
🤖 Prompt for AI Agents
In .cursor/rules/backend-error-handling.mdc around lines 2 to 5, add a one-line
description above the globs field to summarize the rule's purpose. Expand the
guidance bullet into clear, precise wording that explains using try-except
blocks for specific exceptions rather than catching all exceptions, and include
concrete DO and DON’T Python code examples illustrating proper and improper
error handling. Also, add references to related rules for logging and backend
best practices. Confirm whether alwaysApply: false is intentional for manual
adoption.

Comment on lines +2 to +3
globs: *.py
alwaysApply: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Missing description in frontmatter; clarify scope and enforcement.

Add a concise description; confirm intentional change to alwaysApply: false.

Suggested frontmatter:

 ---
-globs: *.py
-alwaysApply: false
+description: Standardize Python logging with structured context, appropriate levels, and safe practices.
+globs: "*.py"
+alwaysApply: false
 ---

Please confirm that disabling automatic enforcement is intended.


Add missing rule description and confirm enforcement setting

The frontmatter in .cursor/rules/backend-logging.mdc is missing a description: field. Please add a concise summary of this rule’s purpose and verify that disabling automatic enforcement (alwaysApply: false) is intentional.

• File: .cursor/rules/backend-logging.mdc (lines 1–3)

Suggested update:

 ---
+description: "Standardize Python logging with structured context, appropriate levels, and safe practices."
  globs: "*.py"
  alwaysApply: false
 ---

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .cursor/rules/backend-logging.mdc at lines 1 to 3, add a concise description
field in the frontmatter summarizing the purpose of the backend logging rule.
Also, review and confirm that the alwaysApply setting is intentionally set to
false to disable automatic enforcement. Update the frontmatter to include the
description and verify the enforcement setting accordingly.

Comment on lines +6 to +13
- Include context in log messages. For example:
```
logger.warning(
"Error occurred during profile update",
error=str(e),
profile_id=profile.id
)
- Use appropriate log levels (INFO, WARNING, ERROR)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen logging guidance: restore log levels, add language hint, show structlog example, warn against PII.

The example lacks a language hint and we removed the “appropriate log levels” bullet that other rules recommend. Let’s add robust examples and PII guidance.

Proposed content refinements:

 - Include context in log messages. For example:
-```
-logger.warning(
-    "Error occurred during profile update",
-    error=str(e),
-    profile_id=profile.id
-)
-```
+```python
+# structlog with contextual, key/value logging
+import structlog
+logger = structlog.get_logger()
+
+try:
+    update_profile(profile)
+except ValidationError as e:
+    logger.warning("Profile update failed", error=str(e), profile_id=profile.id)
+    # handle or return
+except TimeoutError as e:
+    logger.error("Profile update timeout", error=str(e), profile_id=profile.id)
+    raise
+```
+
+- Use appropriate log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL).
+- Avoid logging PII/secrets (passwords, tokens, full addresses, etc.). Prefer IDs/hashes.
+- Correlate logs with request IDs or user IDs when available.
+
+See also:
+- [backend-error-handling.mdc](mdc:.cursor/rules/backend-error-handling.mdc) for exception patterns.
🤖 Prompt for AI Agents
In .cursor/rules/backend-logging.mdc around lines 6 to 13, enhance the logging
guidance by adding a language hint for the code block (use python), restore the
recommendation to use appropriate log levels (DEBUG, INFO, WARNING, ERROR,
CRITICAL), provide a more robust example using structlog with contextual
key/value logging including try-except blocks for different exceptions, and add
warnings about avoiding logging PII or secrets while preferring IDs or hashes.
Also, include advice on correlating logs with request or user IDs and reference
related documentation for error handling patterns.

Comment on lines +85 to +90
### Background Tasks
Uses django-q2 for async task processing:
- Tasks defined in `core/tasks.py`
- Redis as the task broker
- Background processing for AI content generation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Call out required env vars for Redis and background workers

Since django-q2/Redis are required, list expected env vars (Redis URL, queue settings) and reference .env.example entries.

To verify .env.example coverage:


🏁 Script executed:

#!/bin/bash
rg -n --pretty 'REDIS|Q_|DJANGO_Q|AI|STRIPE|ALLAUTH|LOGFIRE|NINJA' .env.example

Length of output: 249


Document required env vars for Redis & django-q2
The Background Tasks section in CLAUDE.md (lines 85–90) lists Redis as the broker but doesn’t show which env vars need to be set. Please update it to call out all required variables and reference their defaults in .env.example. For example:

  • REDIS_URL — Redis connection URL (already in .env.example)
  • Q_CLUSTER_NAME — django-q2 cluster identifier
  • Q_CONCURRENCY — number of worker processes
  • Q_TIMEOUT — task execution timeout (seconds)

Suggested diff in CLAUDE.md:

 ### Background Tasks
 Uses django-q2 for async task processing:
 - Tasks defined in `core/tasks.py`
 - Redis as the task broker
 - Background processing for AI content generation

+#### Required Environment Variables
+- **REDIS_URL**       # Redis broker URL (see `.env.example`)
+- **Q_CLUSTER_NAME**  # Name of the django-q2 cluster
+- **Q_CONCURRENCY**   # Number of worker processes
+- **Q_TIMEOUT**       # Task timeout in seconds
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Background Tasks
Uses django-q2 for async task processing:
- Tasks defined in `core/tasks.py`
- Redis as the task broker
- Background processing for AI content generation
### Background Tasks
Uses django-q2 for async task processing:
- Tasks defined in `core/tasks.py`
- Redis as the task broker
- Background processing for AI content generation
#### Required Environment Variables
- **REDIS_URL** # Redis broker URL (see `.env.example`)
- **Q_CLUSTER_NAME** # Name of the django-q2 cluster
- **Q_CONCURRENCY** # Number of worker processes
- **Q_TIMEOUT** # Task timeout in seconds
🤖 Prompt for AI Agents
In CLAUDE.md around lines 85 to 90, the Background Tasks section mentions Redis
as the broker but does not document the required environment variables. Update
this section to list all necessary env vars for Redis and django-q2
configuration, including REDIS_URL, Q_CLUSTER_NAME, Q_CONCURRENCY, and
Q_TIMEOUT, and reference their default values as defined in .env.example to
provide clear setup instructions.

Comment on lines 18 to 22
migrations.AddField(
model_name='project',
name='enable_automatic_post_submission',
field=models.BooleanField(default=False),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add an index to the flag used in filters.

enable_automatic_post_submission is queried in tasks; add db_index=True to avoid full table scans as projects grow.

Apply:

         migrations.AddField(
             model_name='project',
             name='enable_automatic_post_submission',
-            field=models.BooleanField(default=False),
+            field=models.BooleanField(default=False, db_index=True),
         ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
migrations.AddField(
model_name='project',
name='enable_automatic_post_submission',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='project',
name='enable_automatic_post_submission',
field=models.BooleanField(default=False, db_index=True),
),
🤖 Prompt for AI Agents
In core/migrations/0028_project_enable_automatic_post_generation_and_more.py
around lines 18 to 22, the BooleanField 'enable_automatic_post_submission' lacks
a database index, which can cause full table scans during queries. Add the
argument db_index=True to this field definition to create an index and improve
query performance.

Comment on lines 13 to 17
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Index date_posted to speed ordering and lookups.

This field is used to order and find the last posted blog post; add db_index=True.

Apply:

         migrations.AddField(
             model_name='generatedblogpost',
             name='date_posted',
-            field=models.DateTimeField(blank=True, null=True),
+            field=models.DateTimeField(blank=True, null=True, db_index=True),
         ),

Optional follow-up: add a CheckConstraint ensuring date_posted IS NOT NULL when posted=True to enforce data integrity.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True, db_index=True),
),
🤖 Prompt for AI Agents
In core/migrations/0029_generatedblogpost_date_posted.py around lines 13 to 17,
the date_posted field lacks a database index which is needed to speed up
ordering and lookups. Modify the field definition to include db_index=True.
Optionally, add a CheckConstraint to ensure date_posted is not null when posted
is true to maintain data integrity.

Comment on lines 258 to 265
@property
def generated_blog_posts(self):
return self.generated_blog_posts.all()

@property
def last_posted_blog_post(self):
return self.generated_blog_posts.filter(posted=True).order_by("-date_posted").first()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Property shadows related manager → infinite recursion

The generated_blog_posts property has the same name as the reverse FK manager, so self.generated_blog_posts inside the property resolves back to the property, recursing until a stack-overflow.

-@property
-def generated_blog_posts(self):
-    return self.generated_blog_posts.all()
+# Remove this property entirely – the related manager already
+# provides the queryset.  If you need a convenience wrapper, give
+# it a distinct name:
+# @property
+# def all_generated_posts(self):
+#     return self.generated_blog_posts.all()

Update last_posted_blog_post to use the manager:

-return self.generated_blog_posts.filter(posted=True)...
+return self.generated_blog_posts.filter(posted=True)...

(with the property gone, this now points to the correct manager).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In core/models.py around lines 258 to 265, the generated_blog_posts property
shadows the related manager of the same name, causing infinite recursion when
accessed. Remove the generated_blog_posts property entirely so that
self.generated_blog_posts refers to the related manager. Then update
last_posted_blog_post to directly use self.generated_blog_posts.filter(...)
without the property, ensuring it calls the manager methods correctly.

Comment on lines 229 to 233
profile_id: int,
from_state: str,
to_state: str,
metadata: dict = None,
) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix return type annotation (function returns str).

track_state_change returns strings in multiple branches; update the annotation for accuracy and tooling.

Apply:

-def track_state_change(
+def track_state_change(
     profile_id: int,
     from_state: str,
     to_state: str,
     metadata: dict = None,
-) -> None:
+) -> str:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
profile_id: int,
from_state: str,
to_state: str,
metadata: dict = None,
) -> None:
def track_state_change(
profile_id: int,
from_state: str,
to_state: str,
metadata: dict = None,
) -> str:
🤖 Prompt for AI Agents
In core/tasks.py around lines 229 to 233, the function track_state_change is
annotated to return None, but it actually returns strings in multiple branches.
Update the return type annotation from None to str to accurately reflect the
function's return values and improve type checking.

Comment on lines 276 to 301
<div
data-controller="auto-submission-toggle"
data-auto-submission-toggle-project-id-value="{{ project.id }}"
data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
class=""
>
<div class="flex justify-between items-center">
<p class="max-w-prose text-lg font-semibold text-gray-600">
Enable
</p>
<button
type="button"
data-action="auto-submission-toggle#toggle"
data-auto-submission-toggle-target="toggle"
class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
role="switch"
aria-checked="{{ project.enable_automatic_post_submission|lower }}"
>
<span
data-auto-submission-toggle-target="switch"
class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform"
></span>
</button>
</div>
</div>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Improve switch accessibility and initial visual state.

  • Add aria-labelledby tied to a visible label.
  • Reflect current enabled state in track/knob classes to avoid a wrong initial render flash.
  • Remove empty class on container.

Apply:

-          <div
-            data-controller="auto-submission-toggle"
-            data-auto-submission-toggle-project-id-value="{{ project.id }}"
-            data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
-            class=""
-          >
+          <div
+            data-controller="auto-submission-toggle"
+            data-auto-submission-toggle-project-id-value="{{ project.id }}"
+            data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
+          >
             <div class="flex justify-between items-center">
-              <p class="max-w-prose text-lg font-semibold text-gray-600">
-                Enable
-              </p>
+              <p id="auto-submission-label" class="max-w-prose text-lg font-semibold text-gray-600">
+                Enable automatic submission
+              </p>
               <button
                 type="button"
                 data-action="auto-submission-toggle#toggle"
                 data-auto-submission-toggle-target="toggle"
-                class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
+                class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500 {% if project.enable_automatic_post_submission %}bg-pink-600{% else %}bg-gray-200{% endif %}"
                 role="switch"
                 aria-checked="{{ project.enable_automatic_post_submission|lower }}"
+                aria-labelledby="auto-submission-label"
               >
               <span
                   data-auto-submission-toggle-target="switch"
-                  class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform"
+                  class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform {% if project.enable_automatic_post_submission %}translate-x-5{% else %}translate-x-1{% endif %}"
                 ></span>
               </button>
             </div>
           </div>

To ensure the Stimulus controller also toggles these classes on interaction, please check it updates the track (bg-) and knob (translate-x-) classes:


🏁 Script executed:

#!/bin/bash
fd auto_submission_toggle_controller.js | xargs -I {} sh -c 'echo "--- {} ---"; sed -n "1,200p" "{}"'
rg -n "bg-(pink|gray)|translate-x" -A2 -B2 frontend/src/controllers/auto_submission_toggle_controller.js

Length of output: 2485


Improve switch accessibility and initial visual state.

  • Remove the empty class attribute on the container.
  • Add an id on the label and tie the button’s aria-labelledby to it.
  • Initialize the button track (bg-pink-600/bg-gray-200) and knob (translate-x-5/translate-x-0) based on project.enable_automatic_post_submission to prevent a flash of the wrong state.
  • The Stimulus auto_submission_toggle controller already synchronizes these classes on connect and toggle.

Apply:

--- a/frontend/templates/project/project_settings.html
+++ m/frontend/templates/project/project_settings.html
@@ -276,8 +276,14 @@
-          <div
-            data-controller="auto-submission-toggle"
-            data-auto-submission-toggle-project-id-value="{{ project.id }}"
-            data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
-            class=""
-          >
+          <div
+            data-controller="auto-submission-toggle"
+            data-auto-submission-toggle-project-id-value="{{ project.id }}"
+            data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
+          >
             <div class="flex justify-between items-center">
-              <p class="max-w-prose text-lg font-semibold text-gray-600">
-                Enable
-              </p>
+              <p id="auto-submission-label" class="max-w-prose text-lg font-semibold text-gray-600">
+                Enable automatic submission
+              </p>
               <button
                 type="button"
                 data-action="auto-submission-toggle#toggle"
                 data-auto-submission-toggle-target="toggle"
-                class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
+                class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500 {% if project.enable_automatic_post_submission %}bg-pink-600{% else %}bg-gray-200{% endif %}"
                 role="switch"
-                aria-checked="{{ project.enable_automatic_post_submission|lower }}"
+                aria-checked="{{ project.enable_automatic_post_submission|lower }}"
+                aria-labelledby="auto-submission-label"
               >
               <span
                   data-auto-submission-toggle-target="switch"
-                  class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform"
+                  class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform {% if project.enable_automatic_post_submission %}translate-x-5{% else %}translate-x-0{% endif %}"
                 ></span>
               </button>
             </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
data-controller="auto-submission-toggle"
data-auto-submission-toggle-project-id-value="{{ project.id }}"
data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
class=""
>
<div class="flex justify-between items-center">
<p class="max-w-prose text-lg font-semibold text-gray-600">
Enable
</p>
<button
type="button"
data-action="auto-submission-toggle#toggle"
data-auto-submission-toggle-target="toggle"
class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
role="switch"
aria-checked="{{ project.enable_automatic_post_submission|lower }}"
>
<span
data-auto-submission-toggle-target="switch"
class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform"
></span>
</button>
</div>
</div>
<div
data-controller="auto-submission-toggle"
data-auto-submission-toggle-project-id-value="{{ project.id }}"
data-auto-submission-toggle-enabled-value="{{ project.enable_automatic_post_submission|lower }}"
>
<div class="flex justify-between items-center">
<p id="auto-submission-label" class="max-w-prose text-lg font-semibold text-gray-600">
Enable automatic submission
</p>
<button
type="button"
data-action="auto-submission-toggle#toggle"
data-auto-submission-toggle-target="toggle"
class="inline-flex relative items-center w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500 {% if project.enable_automatic_post_submission %}bg-pink-600{% else %}bg-gray-200{% endif %}"
role="switch"
aria-checked="{{ project.enable_automatic_post_submission|lower }}"
aria-labelledby="auto-submission-label"
>
<span
data-auto-submission-toggle-target="switch"
class="inline-block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform {% if project.enable_automatic_post_submission %}translate-x-5{% else %}translate-x-0{% endif %}"
></span>
</button>
</div>
</div>
🤖 Prompt for AI Agents
In frontend/templates/project/project_settings.html around lines 276 to 301,
remove the empty class attribute from the container div, add an id attribute to
the label paragraph and set the button's aria-labelledby attribute to this id
for better accessibility, and initialize the button's background color and knob
translation classes conditionally based on
project.enable_automatic_post_submission to ensure the switch visually reflects
the correct initial state without flashing the wrong style.


[tool.ruff.lint.per-file-ignores]
"**/migrations/*.py" = ["E501", "F401", "F403", "F405"]
"**/models.py" = ["C901"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Scope C901 ignore to the intended app (don’t blanket-ignore all models).

"**/models.py" will suppress complexity checks across every app. Tighten this to just the target file or use inline noqa on problematic functions.

If only core/models.py needs this, update config:

-[tool.ruff.lint.per-file-ignores]
-"**/migrations/*.py" = ["E501", "F401", "F403", "F405"]
-"**/models.py" = ["C901"]
+[tool.ruff.lint.per-file-ignores]
+"**/migrations/*.py" = ["E501", "F401", "F403", "F405"]
+"core/models.py" = ["C901"]

Alternatively, keep the config strict and add targeted # noqa: C901 to specific complex methods in models.py.


#!/bin/bash
# List all models.py files in the repo
echo "=== models.py files ==="
find . -type f -name models.py

echo
# Show the Ruff per-file-ignores section in pyproject.toml
echo "=== [tool.ruff.lint.per-file-ignores] in pyproject.toml ==="
awk '/\[tool\.ruff\.lint\.per-file-ignores\]/,/^\[/' pyproject.toml || true

echo
# Find the exact line for "**/models.py"
echo "=== Occurrences of \"**/models.py\" ==="
grep -n '"\*\*/models.py"' pyproject.toml || true

🏁 Script executed:

#!/bin/bash
set -e

# 1. List all models.py files (to identify app paths)
echo "=== models.py files ==="
find . -type f -name models.py

# 2. Extract the Ruff per-file-ignores section from pyproject.toml
echo
echo "=== [tool.ruff.lint.per-file-ignores] in pyproject.toml ==="
awk '/\[tool\.ruff\.lint\.per-file-ignores\]/,/^\[/' pyproject.toml

# 3. Find the exact occurrence(s) of "**/models.py" in pyproject.toml
echo
echo "=== Occurrences of \"**/models.py\" ==="
grep -n '"\*\*/models.py"' pyproject.toml || true

Length of output: 522


Scope C901 ignore to core/models.py only

Only core/models.py exists in this repo, so we should tighten the Ruff per-file-ignores entry rather than blanket-ignoring every models.py. Update your pyproject.toml as follows:

 [tool.ruff.lint.per-file-ignores]
 "**/migrations/*.py" = ["E501", "F401", "F403", "F405"]
-"**/models.py" = ["C901"]
+"core/models.py"  = ["C901"]

This ensures complexity checks are only disabled for the intended file.

🤖 Prompt for AI Agents
In pyproject.toml at line 63, the current per-file-ignores entry disables C901
complexity checks for all models.py files. Since only core/models.py exists,
update the entry to target only core/models.py by replacing "**/models.py" with
"core/models.py" to restrict the ignore scope appropriately.

Copy link

@code-review-doctor code-review-doctor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Worth considering though. View full project report here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
core/tasks.py (2)

297-334: Critical: queryset used as model instance; plus improve robustness, type hints, and error handling.

  • blog_post_to_post is a QuerySet, but later used as an instance (submit_blog_post_to_endpoint), which will error.
  • Use first() to get a single post; keep the variable always a model instance or None.
  • Use .exists() when checking QuerySets (echoing prior bot feedback); avoid truthiness evaluation.
  • Add try/except around the external submission call and annotate return type.
-def generate_and_post_blog_post(project_id: int):
-    project = Project.objects.get(id=project_id)
-
-    # first see if there are generated blog posts that are not posted yet
-    blog_post_to_post = GeneratedBlogPost.objects.filter(project=project, posted=False)
-
-    # then see if there are blog post title suggestions without generated blog posts
-    if not blog_post_to_post:
-        ungenerated_blog_post_suggestions = BlogPostTitleSuggestion.objects.filter(
-            project=project, generated_blog_posts__isnull=True
-        )
-        if ungenerated_blog_post_suggestions:
-            ungenerated_blog_post_suggestion = ungenerated_blog_post_suggestions.first()
-            blog_post_to_post = ungenerated_blog_post_suggestion.generate_content(
-                content_type=ungenerated_blog_post_suggestion.content_type
-            )
-
-    # if neither, create a new blog post title suggestion, generate the blog post
-    if not blog_post_to_post:
-        content_type = random.choice([choice[0] for choice in ContentType.choices])
-        suggestions = project.generate_title_suggestions(content_type=content_type, num_titles=1)
-        blog_post_to_post = suggestions[0].generate_content(
-            content_type=suggestions[0].content_type
-        )
-
-    # once you have the generated blog post, submit it to the endpoint
-    if blog_post_to_post:
-        result = blog_post_to_post.submit_blog_post_to_endpoint()
-        if result is True:
-            blog_post_to_post.posted = True
-            blog_post_to_post.date_posted = timezone.now()
-            blog_post_to_post.save(update_fields=["posted", "date_posted"])
-            return f"Posted blog post for {project.name}"
-        else:
-            return f"Failed to post blog post for {project.name}."
-    else:
-        return f"No blog post to post for {project.name}."
+def generate_and_post_blog_post(project_id: int) -> str:
+    project = Project.objects.get(id=project_id)
+
+    # 1) Prefer the earliest unposted generated blog post
+    post = (
+        GeneratedBlogPost.objects
+        .filter(project=project, posted=False)
+        .order_by("id")
+        .first()
+    )
+
+    # 2) Otherwise, generate content from an unused title suggestion
+    if post is None:
+        suggestion = (
+            BlogPostTitleSuggestion.objects
+            .filter(project=project, generated_blog_posts__isnull=True)
+            .order_by("id")
+            .first()
+        )
+        if suggestion:
+            post = suggestion.generate_content(content_type=suggestion.content_type)
+
+    # 3) Otherwise, create a new title suggestion and generate content
+    if post is None:
+        content_type = random.choice([choice[0] for choice in ContentType.choices])
+        suggestions = project.generate_title_suggestions(content_type=content_type, num_titles=1) or []
+        if suggestions:
+            post = suggestions[0].generate_content(content_type=suggestions[0].content_type)
+
+    if post is None:
+        return f"No blog post to post for {project.name}."
+
+    # Submit to endpoint and update state
+    try:
+        result = post.submit_blog_post_to_endpoint()
+    except Exception as e:
+        logger.exception("[AutoPost] Exception while submitting blog post", project_id=project.id, error=str(e))
+        return f"Failed to post blog post for {project.name}."
+
+    if result is True:
+        post.posted = True
+        post.date_posted = timezone.now()
+        post.save(update_fields=["posted", "date_posted"])
+        return f"Posted blog post for {project.name}"
+    else:
+        return f"Failed to post blog post for {project.name}."

Optional: if ContentType is a TextChoices enum, consider random.choice([c.value for c in ContentType]) for clarity.


241-245: Fix return type annotation (function returns str).
track_state_change returns strings in multiple branches; update the annotation for accuracy and tooling.

 def track_state_change(
     profile_id: int,
     from_state: str,
     to_state: str,
     metadata: dict = None,
-) -> None:
+) -> str:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb35ec5 and 8394d5e.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • core/models.py (5 hunks)
  • core/tasks.py (3 hunks)
  • requirements.txt (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • requirements.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/models.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-05T14:18:22.407Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/backend.mdc:0-0
Timestamp: 2025-07-05T14:18:22.407Z
Learning: Applies to core/tasks.py : Use django-q2 syntax for workers

Applied to files:

  • core/tasks.py
📚 Learning: 2025-06-23T13:57:13.615Z
Learnt from: CR
PR: rasulkireev/marketing-agents#0
File: .cursor/rules/django-async-tasks.mdc:0-0
Timestamp: 2025-06-23T13:57:13.615Z
Learning: In Django projects using Django-Q for async tasks, never pass the HttpRequest object to a background task because it is not serializable and will cause pickling errors. Instead, extract only the specific, serializable data needed (such as user ID, cookies, session data, GET/POST parameters, or headers) and pass those to the task.

Applied to files:

  • core/tasks.py
🔇 Additional comments (1)
core/tasks.py (1)

1-1: Imports look correct and aligned with usage.
No issues spotted; additions are used below (calendar, random, timezone, models).

Also applies to: 3-3, 9-9, 13-22

Comment on lines 276 to 296
def schedule_blog_post_posting():
projects = Project.objects.filter(enable_automatic_post_submission=True)
for project in projects:
if not project.has_auto_submission_setting:
continue

now = timezone.now()
last_post_date = project.last_posted_blog_post.date_posted
time_since_last_post_in_seconds = (now - last_post_date).total_seconds()

days_in_month = calendar.monthrange(now.year, now.month)[1]
time_between_posts_in_seconds = int(
days_in_month
* (24 * 60 * 60)
/ project.auto_submission_settings.latest("created_at").posts_per_month
)

if time_since_last_post_in_seconds > time_between_posts_in_seconds:
async_task(generate_and_post_blog_post, project.id)


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Guard against None/Naive last_post_date and division-by-zero; enqueue immediately if never posted.
As written, last_post_date can be None (new projects or legacy rows with null date_posted), causing a TypeError on subtraction. Also guard posts_per_month against 0/None to avoid ZeroDivisionError. Optional: move now outside loop and use earliest immediate enqueue when no prior post. Async usage complies with Django-Q2; consider grouping tasks by project for observability.

 def schedule_blog_post_posting():
-    projects = Project.objects.filter(enable_automatic_post_submission=True)
-    for project in projects:
-        if not project.has_auto_submission_setting:
-            continue
-
-        now = timezone.now()
-        last_post_date = project.last_posted_blog_post.date_posted
-        time_since_last_post_in_seconds = (now - last_post_date).total_seconds()
-
-        days_in_month = calendar.monthrange(now.year, now.month)[1]
-        time_between_posts_in_seconds = int(
-            days_in_month
-            * (24 * 60 * 60)
-            / project.auto_submission_settings.latest("created_at").posts_per_month
-        )
-
-        if time_since_last_post_in_seconds > time_between_posts_in_seconds:
-            async_task(generate_and_post_blog_post, project.id)
+    projects = Project.objects.filter(enable_automatic_post_submission=True)
+    now = timezone.now()
+    for project in projects:
+        if not project.has_auto_submission_setting:
+            continue
+
+        last_post = getattr(getattr(project, "last_posted_blog_post", None), "date_posted", None)
+
+        # If we've never posted, enqueue immediately
+        if not last_post:
+            async_task(generate_and_post_blog_post, project.id)
+            continue
+
+        settings_obj = project.auto_submission_settings.latest("created_at")
+        posts_per_month = getattr(settings_obj, "posts_per_month", None)
+        if not posts_per_month or posts_per_month <= 0:
+            # Avoid division by zero and invalid config
+            continue
+
+        days_in_month = calendar.monthrange(now.year, now.month)[1]
+        time_between_posts_in_seconds = int(days_in_month * 24 * 60 * 60 / posts_per_month)
+
+        if (now - last_post).total_seconds() >= time_between_posts_in_seconds:
+            async_task(generate_and_post_blog_post, project.id)

If you'd like, I can add lightweight logging and task naming/grouping for traceability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def schedule_blog_post_posting():
projects = Project.objects.filter(enable_automatic_post_submission=True)
for project in projects:
if not project.has_auto_submission_setting:
continue
now = timezone.now()
last_post_date = project.last_posted_blog_post.date_posted
time_since_last_post_in_seconds = (now - last_post_date).total_seconds()
days_in_month = calendar.monthrange(now.year, now.month)[1]
time_between_posts_in_seconds = int(
days_in_month
* (24 * 60 * 60)
/ project.auto_submission_settings.latest("created_at").posts_per_month
)
if time_since_last_post_in_seconds > time_between_posts_in_seconds:
async_task(generate_and_post_blog_post, project.id)
def schedule_blog_post_posting():
projects = Project.objects.filter(enable_automatic_post_submission=True)
now = timezone.now()
for project in projects:
if not project.has_auto_submission_setting:
continue
last_post = getattr(getattr(project, "last_posted_blog_post", None), "date_posted", None)
# If we've never posted, enqueue immediately
if not last_post:
async_task(generate_and_post_blog_post, project.id)
continue
settings_obj = project.auto_submission_settings.latest("created_at")
posts_per_month = getattr(settings_obj, "posts_per_month", None)
if not posts_per_month or posts_per_month <= 0:
# Avoid division by zero and invalid config
continue
days_in_month = calendar.monthrange(now.year, now.month)[1]
time_between_posts_in_seconds = int(days_in_month * 24 * 60 * 60 / posts_per_month)
if (now - last_post).total_seconds() >= time_between_posts_in_seconds:
async_task(generate_and_post_blog_post, project.id)
🤖 Prompt for AI Agents
In core/tasks.py around lines 276 to 296, the code does not handle cases where
last_post_date is None or naive, causing errors on date subtraction, and does
not guard against posts_per_month being zero or None, risking division by zero.
Fix this by checking if last_post_date is None or naive and enqueue the task
immediately if so. Also validate posts_per_month before division, skipping or
handling zero/None values appropriately. Move the now timestamp outside the loop
for efficiency. Optionally, add logging and group async tasks by project for
better traceability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/migrations/0028_generatedblogpost_date_posted_and_more.py (1)

23-32: Review migration defaults and rollout strategy

  • By adding these BooleanFields with default=…, Django will backfill the default value into existing rows.
    • If you only want new projects to default to True, add a data migration to set enable_automatic_post_generation=False on existing records.
    def _set_auto_gen_false_for_existing(apps, schema_editor):
        Project = apps.get_model('core', 'Project')
        Project.objects.all().update(enable_automatic_post_generation=False)
    
    class Migration(migrations.Migration):
        operations = [
            # … your AddField operations …
            migrations.RunPython(_set_auto_gen_false_for_existing, reverse_code=migrations.RunPython.noop),
        ]
  • Since you’re on Django 5.2.5 (≥ 4.2), you can avoid expensive table rewrites by using db_default on these BooleanFields:
    migrations.AddField(
        model_name='project',
        name='enable_automatic_post_generation',
    -   field=models.BooleanField(default=True),
    +   field=models.BooleanField(default=True, db_default=True),
    ),
    migrations.AddField(
        model_name='project',
        name='enable_automatic_post_submission',
    -   field=models.BooleanField(default=False),
    +   field=models.BooleanField(default=False, db_default=False),
    ),
  • Optional (PostgreSQL only): add a partial index to speed up scheduler queries when both flags are True:
    migrations.AddIndex(
        model_name='project',
        index=models.Index(
            name='idx_project_auto_flags_true',
            fields=['id'],
            condition=models.Q(
                enable_automatic_post_generation=True,
                enable_automatic_post_submission=True,
            ),
        ),
    ),
  • Please confirm your database backend and version (e.g. PostgreSQL ≥ 9.4+) to ensure support for db_default and partial indexes, and verify that the backfill behavior aligns with your rollout plan.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8394d5e and 643bd90.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • core/migrations/0028_generatedblogpost_date_posted_and_more.py (1 hunks)
  • core/models.py (6 hunks)
  • core/tasks.py (3 hunks)
  • frontend/templates/project/project_settings.html (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/templates/project/project_settings.html
  • core/tasks.py
  • core/models.py
🔇 Additional comments (2)
core/migrations/0028_generatedblogpost_date_posted_and_more.py (2)

19-22: LGTM: experimental_features flag.

Field and default look good.


8-10: Ignore typo concern in migration dependency
The dependency ('core', '0027_autosubmissionsetting_delete_autosubmittionsetting') matches the existing filename core/migrations/0027_autosubmissionsetting_delete_autosubmittionsetting.py. No change is needed.

Likely an incorrect or invalid review comment.

Comment on lines +13 to +17
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Index date_posted to speed up scheduler lookups.

Tasks will likely filter by date_posted or isnull; add an index.

-            field=models.DateTimeField(blank=True, null=True),
+            field=models.DateTimeField(blank=True, null=True, db_index=True),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='generatedblogpost',
name='date_posted',
field=models.DateTimeField(blank=True, null=True, db_index=True),
),
🤖 Prompt for AI Agents
In core/migrations/0028_generatedblogpost_date_posted_and_more.py around lines
13 to 17, the date_posted field lacks an index which will slow down scheduler
lookups filtering by this field. Add db_index=True to the DateTimeField
definition for date_posted to create a database index and improve query
performance.

Copy link

@code-review-doctor code-review-doctor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth considering. View full project report here.

blog_posts_to_post = GeneratedBlogPost.objects.filter(project=project, posted=False)

if blog_posts_to_post.exists():
blog_post_to_post = blog_posts_to_post.first()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking ungenerated_blog_post_suggestions truthiness is less efficient than checking ungenerated_blog_post_suggestions.exists() or ungenerated_blog_post_suggestions is not None. Checking queryset truthiness evaluates the queryset, therefore reading the records from the database. More.

@rasulkireev rasulkireev merged commit 6c3f127 into main Aug 9, 2025
1 of 2 checks passed
@sentry
Copy link

sentry bot commented Aug 9, 2025

Suspect Issues

This pull request was deployed and Sentry observed the following issues:

Did you find this useful? React with a 👍 or 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants