Add document
This commit is contained in:
@@ -1,3 +1,21 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Document, DocumentSegment, Organization
|
||||||
|
|
||||||
# Register your models here.
|
class DocumentSegmentInline(admin.StackedInline):
|
||||||
|
model = DocumentSegment
|
||||||
|
extra = 1
|
||||||
|
ordering = ['order']
|
||||||
|
fields = ('segment_type', 'content', 'order')
|
||||||
|
|
||||||
|
class DocumentAdmin(admin.ModelAdmin):
|
||||||
|
inlines = [DocumentSegmentInline]
|
||||||
|
list_display = ('organization', 'created_at', 'modified_at')
|
||||||
|
search_fields = ['organization__name']
|
||||||
|
readonly_fields = ('created_at', 'modified_at')
|
||||||
|
|
||||||
|
class OrganizationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'email', 'industry_sector')
|
||||||
|
search_fields = ['name', 'email']
|
||||||
|
|
||||||
|
admin.site.register(Document, DocumentAdmin)
|
||||||
|
admin.site.register(Organization, OrganizationAdmin)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import uuid
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from localflavor.br.br_states import STATE_CHOICES
|
from localflavor.br.br_states import STATE_CHOICES
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
class UuidModel(models.Model):
|
class UuidModel(models.Model):
|
||||||
@@ -41,92 +42,6 @@ class CreatedBy(models.Model):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class Address(models.Model):
|
|
||||||
address = models.CharField(
|
|
||||||
'endereço',
|
|
||||||
max_length=100,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
address_number = models.IntegerField('número', null=True, blank=True)
|
|
||||||
complement = models.CharField(
|
|
||||||
'complemento',
|
|
||||||
max_length=100,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
district = models.CharField(
|
|
||||||
'bairro',
|
|
||||||
max_length=100,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
city = models.CharField('cidade', max_length=100, null=True, blank=True)
|
|
||||||
uf = models.CharField(
|
|
||||||
'UF',
|
|
||||||
max_length=2,
|
|
||||||
choices=STATE_CHOICES,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
cep = models.CharField('CEP', max_length=9, null=True, blank=True)
|
|
||||||
country = models.CharField(
|
|
||||||
'país',
|
|
||||||
max_length=50,
|
|
||||||
default='Brasil',
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def to_dict_base(self):
|
|
||||||
return {
|
|
||||||
'address': self.address,
|
|
||||||
'address_number': self.address_number,
|
|
||||||
'complement': self.complement,
|
|
||||||
'district': self.district,
|
|
||||||
'city': self.city,
|
|
||||||
'uf': self.uf,
|
|
||||||
'cep': self.cep,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Document(models.Model):
|
|
||||||
cpf = models.CharField(
|
|
||||||
'CPF',
|
|
||||||
max_length=11,
|
|
||||||
unique=True,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
rg = models.CharField('RG', max_length=11, null=True, blank=True)
|
|
||||||
cnh = models.CharField('CNH', max_length=20, null=True, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def to_dict_base(self):
|
|
||||||
return {
|
|
||||||
'cpf': self.cpf,
|
|
||||||
'rg': self.rg,
|
|
||||||
'cnh': self.cnh,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Active(models.Model):
|
|
||||||
active = models.BooleanField('ativo', default=True)
|
|
||||||
exist_deleted = models.BooleanField(
|
|
||||||
'existe/deletado',
|
|
||||||
default=True,
|
|
||||||
help_text='Se for True o item existe. Se for False o item foi deletado.'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Organization(models.Model):
|
class Organization(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
@@ -155,3 +70,57 @@ class Organization(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentSegment(models.Model):
|
||||||
|
SEGMENT_TYPES = (
|
||||||
|
('title', 'Title'),
|
||||||
|
('subtitle', 'Subtitle'),
|
||||||
|
('h1', 'Header 1'),
|
||||||
|
('h2', 'Header 2'),
|
||||||
|
('h3', 'Header 3'),
|
||||||
|
('body', 'Body Text'),
|
||||||
|
('quote', 'Quote')
|
||||||
|
)
|
||||||
|
|
||||||
|
document = models.ForeignKey('Document', on_delete=models.CASCADE, related_name='segments')
|
||||||
|
segment_type = models.CharField(max_length=20, choices=SEGMENT_TYPES)
|
||||||
|
content = models.TextField()
|
||||||
|
order = models.PositiveIntegerField()
|
||||||
|
modified_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['order']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.get_segment_type_display()} - {self.content[:50]}"
|
||||||
|
|
||||||
|
|
||||||
|
class Document(models.Model):
|
||||||
|
organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='documents')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Document for {self.organization.name}"
|
||||||
|
|
||||||
|
def add_segment(self, segment_type, content, position=None):
|
||||||
|
"""
|
||||||
|
Add a new segment at the specified position.
|
||||||
|
If position is None, append to the end.
|
||||||
|
"""
|
||||||
|
if position is None:
|
||||||
|
# Get the highest order and add 1
|
||||||
|
last_order = self.segments.aggregate(models.Max('order'))['order__max']
|
||||||
|
new_order = 1 if last_order is None else last_order + 1
|
||||||
|
else:
|
||||||
|
# Move all segments at and after the position up by 1
|
||||||
|
self.segments.filter(order__gte=position).update(order=models.F('order') + 1)
|
||||||
|
new_order = position
|
||||||
|
|
||||||
|
return self.segments.create(
|
||||||
|
segment_type=segment_type,
|
||||||
|
content=content,
|
||||||
|
order=new_order
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
90
backend/core/templates/document.html
Normal file
90
backend/core/templates/document.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="document-container">
|
||||||
|
<header class="document-header">
|
||||||
|
<h1>{{ organization.name }}</h1>
|
||||||
|
<div class="document-meta">
|
||||||
|
<p>Created: {{ document.created_at|date:"F j, Y" }}</p>
|
||||||
|
<p>Last modified: {{ document.modified_at|date:"F j, Y" }}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article class="document-content">
|
||||||
|
{% for segment in segments %}
|
||||||
|
{% if segment.segment_type == 'title' %}
|
||||||
|
<h1 class="document-title">{{ segment.content }}</h1>
|
||||||
|
{% elif segment.segment_type == 'subtitle' %}
|
||||||
|
<h2 class="document-subtitle">{{ segment.content }}</h2>
|
||||||
|
{% elif segment.segment_type == 'h1' %}
|
||||||
|
<h2 class="document-h1">{{ segment.content }}</h2>
|
||||||
|
{% elif segment.segment_type == 'h2' %}
|
||||||
|
<h3 class="document-h2">{{ segment.content }}</h3>
|
||||||
|
{% elif segment.segment_type == 'h3' %}
|
||||||
|
<h4 class="document-h3">{{ segment.content }}</h4>
|
||||||
|
{% elif segment.segment_type == 'quote' %}
|
||||||
|
<blockquote class="document-quote">{{ segment.content }}</blockquote>
|
||||||
|
{% else %}
|
||||||
|
<p class="document-body">{{ segment.content }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.document-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-meta {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-subtitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-quote {
|
||||||
|
border-left: 4px solid #ccc;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-body {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
@@ -8,4 +8,6 @@ urlpatterns = [
|
|||||||
path('', v.index, name='index'),
|
path('', v.index, name='index'),
|
||||||
path('signup/', v.signup, name='signup'),
|
path('signup/', v.signup, name='signup'),
|
||||||
path('thankyou/', v.thankyou, name='thankyou'),
|
path('thankyou/', v.thankyou, name='thankyou'),
|
||||||
|
# url document/ recieves a parameter named 'uuid' and passes it to the view
|
||||||
|
path('document/<uuid:uuid>/', v.document, name='document'),
|
||||||
]
|
]
|
||||||
|
|||||||
90
templates/document.html
Normal file
90
templates/document.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="document-container">
|
||||||
|
<header class="document-header">
|
||||||
|
<h1>{{ organization.name }}</h1>
|
||||||
|
<div class="document-meta">
|
||||||
|
<p>Created: {{ document.created_at|date:"F j, Y" }}</p>
|
||||||
|
<p>Last modified: {{ document.modified_at|date:"F j, Y" }}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article class="document-content">
|
||||||
|
{% for segment in segments %}
|
||||||
|
{% if segment.segment_type == 'title' %}
|
||||||
|
<h1 class="document-title">{{ segment.content }}</h1>
|
||||||
|
{% elif segment.segment_type == 'subtitle' %}
|
||||||
|
<h2 class="document-subtitle">{{ segment.content }}</h2>
|
||||||
|
{% elif segment.segment_type == 'h1' %}
|
||||||
|
<h2 class="document-h1">{{ segment.content }}</h2>
|
||||||
|
{% elif segment.segment_type == 'h2' %}
|
||||||
|
<h3 class="document-h2">{{ segment.content }}</h3>
|
||||||
|
{% elif segment.segment_type == 'h3' %}
|
||||||
|
<h4 class="document-h3">{{ segment.content }}</h4>
|
||||||
|
{% elif segment.segment_type == 'quote' %}
|
||||||
|
<blockquote class="document-quote">{{ segment.content }}</blockquote>
|
||||||
|
{% else %}
|
||||||
|
<p class="document-body">{{ segment.content }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.document-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-meta {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-subtitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-quote {
|
||||||
|
border-left: 4px solid #ccc;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-body {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user