base setup

This commit is contained in:
2026-01-07 12:09:20 +05:30
commit 0c275efea1
278 changed files with 11228 additions and 0 deletions

View File

View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from .models import (Blog)
@admin.register(Blog)
class BlogModelAdmin(admin.ModelAdmin):
list_display = ('title','published_on','is_draft','is_active')

View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class BlogsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'at_django_boilerplate.blogs'

View File

@@ -0,0 +1,45 @@
import random
import requests
from django.utils import timezone
from django.core.files.base import ContentFile
from faker import Faker
from .models import Blog
from accounts.models import CustomUser
fake = Faker()
def create_dummy_blogs(n=20):
users = list(CustomUser.objects.filter(is_active=True))
if not users:
print("No active users found. Please create users first.")
return
for i in range(n):
title = fake.sentence(nb_words=6)
content = fake.paragraph(nb_sentences=10)
meta_title = title
meta_description = fake.text(max_nb_chars=180)
meta_keywords = ', '.join(fake.words(nb=5))
# Download a random image
image_url = "https://source.unsplash.com/600x400/?realestate,home,apartment"
response = requests.get(image_url)
blog = Blog(
title=title,
content=content,
meta_title=meta_title,
meta_description=meta_description,
meta_keywords=meta_keywords,
published_on=timezone.now(),
published_by=random.choice(users),
is_draft=False,
is_active=True,
)
if response.status_code == 200:
image_name = f"blog_image_{i}.jpg"
blog.cover_image.save(image_name, ContentFile(response.content), save=False)
blog.save()
print(f"Created blog: {title}")

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.2.6 on 2025-12-29 05:20
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Blog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('published_on', models.DateTimeField(auto_created=True, default=django.utils.timezone.now)),
('title', models.CharField(blank=True, max_length=80, null=True)),
('content', models.TextField(blank=True, null=True)),
('cover_image', models.ImageField(blank=True, null=True, upload_to='blogs')),
('meta_title', models.CharField(blank=True, max_length=80, null=True)),
('meta_description', models.CharField(blank=True, max_length=180, null=True)),
('meta_keywords', models.CharField(blank=True, max_length=100, null=True)),
('is_draft', models.BooleanField(default=True)),
('is_active', models.BooleanField(default=False)),
('published_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.6 on 2026-01-05 04:33
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blogs', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='blog',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.6 on 2026-01-05 05:39
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blogs', '0002_alter_blog_id'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='blog',
options={'ordering': ['-published_on'], 'verbose_name': 'Blog Post', 'verbose_name_plural': 'Blog Posts'},
),
migrations.AddIndex(
model_name='blog',
index=models.Index(fields=['-published_on'], name='blogs_blog_publish_fdee8e_idx'),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.6 on 2026-01-07 04:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('blogs', '0003_alter_blog_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='blog',
options={},
),
migrations.RemoveIndex(
model_name='blog',
name='blogs_blog_publish_fdee8e_idx',
),
]

View File

@@ -0,0 +1,17 @@
from django.db import models
from django.utils import timezone
from at_django_boilerplate.utils.mixins import UUIDMixin
class Blog(UUIDMixin):
title = models.CharField(max_length=80,null=True,blank=True)
content = models.TextField(null=True,blank=True)
cover_image = models.ImageField(upload_to='blogs',null=True,blank=True)
meta_title = models.CharField(max_length=80,null=True,blank=True)
meta_description = models.CharField(max_length=180,null=True,blank=True)
meta_keywords = models.CharField(max_length=100,null=True,blank=True)
published_on = models.DateTimeField(auto_created=True,default=timezone.now)
published_by = models.ForeignKey('accounts.CustomUser',null=True,blank=True,on_delete=models.DO_NOTHING)
is_draft = models.BooleanField(default=True)
is_active = models.BooleanField(default=False)

View File

@@ -0,0 +1,45 @@
{% extends "public_base.html" %}
{% load static %}
{% block title %}{{ meta_title }}{% endblock %}
{% block meta %}
<meta name="description" content="{{ meta_description }}">
<meta property="og:title" content="{{ meta_title }}">
<meta property="og:description" content="{{ meta_description }}">
<meta name="twitter:title" content="{{ meta_title }}">
<meta name="twitter:description" content="{{ meta_description }}">
{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto px-6 py-16">
<!-- Back Button -->
<div class="mb-6">
<a href="{% url 'blogs_list' %}"
class="inline-block px-4 py-2 bg-[#0097b2] text-white rounded-md hover:bg-[#007a94] transition-colors duration-300">
&larr; Back to Blogs
</a>
</div>
<article class="bg-white shadow-lg rounded-lg overflow-hidden">
<header class="px-8 pt-8 pb-6 border-b border-gray-200">
<h1 class="text-4xl font-extrabold text-gray-900 tracking-tight leading-tight mb-2">
{{ blog.title }}
</h1>
<time class="text-gray-500 text-sm uppercase tracking-wide" datetime="{{ blog.published_on }}">
{{ blog.published_on|date:"F d, Y" }}
</time>
</header>
{% if blog.cover_image %}
<figure class="w-full h-96 overflow-hidden rounded-b-lg">
<img src="{{ blog.cover_image.url }}" alt="{{ blog.title }}" class="object-cover w-full h-full transition-transform duration-500 hover:scale-105" />
</figure>
{% endif %}
<section class="prose max-w-none px-8 py-10 text-gray-700">
{{ blog.content|linebreaks }}
</section>
</article>
</div>
{% endblock %}

View File

@@ -0,0 +1,233 @@
{% extends 'public_base.html' %}
{% block title %}{{ meta_title|default:"Our Blog - RegisterYourStartup" }}{% endblock %}
{% block meta_description %}{{ meta_description|default:"Read the latest articles, guides, and updates on company registration, compliance, startups, and business growth from RegisterYourStartup." }}{% endblock %}
{% block content %}
<style>
/* Existing styles unchanged */
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.hover-lift {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hover-lift:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.gradient-text {
background: linear-gradient(90deg, #4f46e5, #7c3aed);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.blog-card-img {
height: 220px;
object-fit: cover;
transition: transform 0.4s ease;
}
.blog-card:hover .blog-card-img {
transform: scale(1.05);
}
/* Search bar styles - pure CSS only */
.search-form {
max-width: 680px;
margin: 0 auto;
}
.search-wrapper {
position: relative;
}
.search-input {
width: 100%;
padding: 16px 60px 16px 24px;
font-size: 1.1rem;
border: 2px solid #e0e7ff;
border-radius: 9999px;
background-color: #ffffff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.15);
}
.search-button {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: #4f46e5;
color: white;
border: none;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: default;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none; /* Button is decorative only */
}
.search-button i {
font-size: 1.1rem;
}
/* Hide non-matching cards */
.blog-card.hidden {
display: none;
}
/* No results message */
.no-results {
grid-column: 1 / -1;
text-align: center;
padding: 4rem 1rem;
color: #6b7280;
}
.no-results i {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.3;
}
</style>
<!-- Hero Section -->
<section class="py-16 md:py-24 bg-[#B6C3FD] relative overflow-hidden">
<div class="container mx-auto px-4">
<div class="text-center max-w-4xl mx-auto">
<h1 class="text-4xl md:text-6xl font-extrabold text-gray-900 mb-6">
Our <span class="gradient-text">Blog</span>
</h1>
<p class="text-xl text-gray-700 mb-8">
Insights, guides, and updates on company registration, startup compliance, taxation, funding, and business growth in India.
</p>
</div>
<div class="absolute inset-0 pointer-events-none">
<div class="absolute top-10 left-10 w-64 h-64 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob"></div>
<div class="absolute top-40 right-10 w-72 h-72 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-2000"></div>
<div class="absolute -bottom-8 left-20 w-72 h-72 bg-indigo-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-4000"></div>
</div>
</div>
</section>
<!-- Client-Side Search Bar -->
<section class="py-10 bg-gray-50">
<div class="container mx-auto px-4">
<div class="search-form">
<div class="search-wrapper">
<input
type="text"
id="blog-search"
placeholder="Search articles, guides, compliance tips..."
class="search-input"
aria-label="Search blog posts"
autocomplete="off"
>
<div class="search-button" aria-hidden="true">
<i class="fas fa-search"></i>
</div>
</div>
</div>
<!-- Live feedback (optional) -->
<div class="text-center mt-4 text-sm text-gray-600" id="search-feedback" style="display: none;">
Showing results for: <strong id="search-term"></strong>
</div>
</div>
</section>
<section id="blog-posts" class="py-16 bg-gray-50">
<div class="container mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" id="blog-grid">
{% for blog in blogs %}
<article class="bg-white rounded-2xl shadow-lg overflow-hidden hover-lift blog-card"
data-title="{{ blog.title|lower }}">
{% if blog.cover_image %}
<a href="{% url 'blog_detail' pk=blog.pk %}">
<img src="{{ blog.cover_image.url }}" alt="{{ blog.title }}" class="w-full blog-card-img">
</a>
{% else %}
<a href="{% url 'blog_detail' pk=blog.pk %}">
<div class="w-full h-56 bg-gradient-to-br from-indigo-200 to-purple-200 flex items-center justify-center">
<i class="fas fa-newspaper text-5xl text-indigo-600 opacity-50"></i>
</div>
</a>
{% endif %}
<div class="p-6">
<h3 class="text-2xl font-bold text-gray-900 mb-3">
<a href="{% url 'blog_detail' pk=blog.pk %}" class="hover:text-indigo-600 transition">
{{ blog.title }}
</a>
</h3>
<a href="{% url 'blog_detail' pk=blog.pk %}" class="text-indigo-600 font-medium hover:text-indigo-800 transition inline-flex items-center">
Read More <i class="fas fa-arrow-right ml-2"></i>
</a>
</div>
</article>
{% empty %}
<div class="col-span-full text-center py-20">
<i class="fas fa-book-open text-6xl text-gray-300 mb-6"></i>
<p class="text-2xl text-gray-600">No blog posts available yet. Stay tuned!</p>
</div>
{% endfor %}
</div>
<!-- No results message (initially hidden) -->
<div class="no-results" id="no-results" style="display: none;">
<i class="fas fa-search text-gray-300"></i>
<p class="text-2xl">No posts found matching your search.</p>
<p class="text-lg mt-2">Try different keywords.</p>
</div>
</div>
</section>
<!-- Client-side Search Script -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const searchInput = document.getElementById('blog-search');
const blogGrid = document.getElementById('blog-grid');
const blogCards = blogGrid.querySelectorAll('.blog-card');
const noResults = document.getElementById('no-results');
const feedback = document.getElementById('search-feedback');
const searchTermSpan = document.getElementById('search-term');
searchInput.addEventListener('input', function () {
const query = this.value.trim().toLowerCase();
let visibleCount = 0;
blogCards.forEach(card => {
const title = card.getAttribute('data-title');
if (title.includes(query)) {
card.classList.remove('hidden');
visibleCount++;
} else {
card.classList.add('hidden');
}
});
// Toggle no-results message
if (visibleCount === 0 && query.length > 0) {
noResults.style.display = 'block';
} else {
noResults.style.display = 'none';
}
// Optional live feedback
if (query.length > 0) {
feedback.style.display = 'block';
searchTermSpan.textContent = query;
} else {
feedback.style.display = 'none';
}
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,8 @@
from django.urls import path
from .import views
urlpatterns = [
path('', views.BlogsListView.as_view(), name='blogs_list'),
path('blogs/<uuid:pk>/', views.BlogDetailView.as_view(), name='blog_detail'),
]

View File

@@ -0,0 +1,29 @@
from django.views.generic import ListView,DetailView
from at_django_boilerplate.backend_admin.models import SEOConfiguration
# Create your views here.
from at_django_boilerplate.blogs.models import Blog
class BlogsListView(ListView):
model = Blog
template_name = 'blogs_list.html'
context_object_name = 'blogs'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
seo = SEOConfiguration.objects.first()
context["meta_title"] = seo.blog_meta_title if seo else "Blog - RegisterYourStartup"
context["meta_description"] = seo.blog_meta_description if seo else "Read articles and updates from Zestato."
return context
class BlogDetailView(DetailView):
model = Blog
template_name = 'blog_detail.html'
context_object_name = 'blog'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
blog = self.get_object()
context["meta_title"] = blog.title
context["meta_description"] = blog.meta_description if hasattr(blog, 'meta_description') else blog.short_description[:160]
return context