Add App Contracts

This commit is contained in:
2026-02-19 22:48:53 -05:00
parent a841e4885e
commit 222bf97d0b
30 changed files with 1177 additions and 4 deletions

View File

@@ -19,6 +19,7 @@ INSTALLED_APPS = [
"rest_framework_api_key", "rest_framework_api_key",
'socials', 'socials',
'events', 'events',
'contracts',
'config', 'config',
# 'academia_nuts', # 'academia_nuts',
# 'leg_info', # 'leg_info',

View File

@@ -23,6 +23,7 @@ from django.urls import path, include
urlpatterns = [ urlpatterns = [
path('socials/', include('socials.urls')), path('socials/', include('socials.urls')),
path('events/', include('events.urls')), path('events/', include('events.urls')),
path('contracts/', include('contracts.urls')),
path('digimon/', admin.site.urls), path('digimon/', admin.site.urls),
] + static (settings.MEDIA_URL, document_root = settings.MEDIA_ROOT) ] + static (settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
# + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

0
contracts/__init__.py Normal file
View File

15
contracts/admin.py Normal file
View File

@@ -0,0 +1,15 @@
from django.contrib import admin
from .models import *
class ContractAdmin(admin.ModelAdmin):
# prepopulated_fields = {"slug": ("shortname",)}
list_display = ("notice_id", "pub_date")
# Register your models here.
admin.site.register(Paragraph)
admin.site.register(OriginalContract)
admin.site.register(Contract, ContractAdmin)
admin.site.register(Company)

6
contracts/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ContractsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'contracts'

415
contracts/digitools.py Normal file
View File

@@ -0,0 +1,415 @@
import os, sys
from datetime import datetime, timedelta
from dateutil import relativedelta
from time import sleep
import pytz
from lxml import html
from pprint import pprint as ppr
import django
from xvfbwrapper import Xvfb
from selenium import webdriver as wd
from events.models import Event as DSEvent, Organization, Promo, Scraper, Calendar
# tz = pytz.timezone("US/Central")
plus_one_month = relativedelta.relativedelta(months=1)
odt_next_month = datetime.now() + plus_one_month
def translateMonth(month):
MONTHS = [
('Januar', 'JÄN', 'January'),
('Februar', 'FEBR', 'February'),
('März', 'MRZ', 'March'),
('April', 'APR', 'April'),
('Mai', 'MAI', 'May'),
('Juni', 'JUN', 'June'),
('Juli', 'JUL', 'July'),
('August', 'AUG', 'August'),
('September', 'SEP', 'September'),
('Oktober', 'OKT', 'October'),
('November', 'NOV', 'November'),
('Dezember', 'DEZ', 'December'),
]
for mon in MONTHS:
if month == mon[1]:
return mon[2]
# Get Scraper name, item count and online_calendar (virtcal)
def getScraper(venue, website, cal):
virtcal = Calendar.objects.get(shortcode='000')
ncal = Calendar.objects.get(shortcode=cal)
try:
scraper, created = Scraper.objects.get_or_create(
name=venue.name,
website=website,
calendar = ncal,
items = 0,
new_items = 0,
last_ran = datetime.now(),
)
venue.cal = ncal
venue.save()
except Exception as e:
print(e)
scraper = Scraper.objects.get(name=venue.name)
num_of_events = DSEvent.objects.filter(scraper=scraper)
scraper.items = len(num_of_events)
scraper.save()
print("Scraper: ", scraper)
return scraper, scraper.items, virtcal
# Update item_count of the Scraper at the end of the scrape
def updateScraper(scraper, item_count_start):
num_of_events = DSEvent.objects.filter(scraper=scraper)
scraper.items = len(num_of_events)
scraper.new_items = len(num_of_events) - item_count_start
scraper.last_ran = datetime.now()
scraper.save()
print("Scaper Updated")
return
# Get site HTML content for XPATH travel
def getSource(browser, link):
browser.get(link)
sleep(6)
ps = html.fromstring(browser.page_source)
return ps
# Get Selenium Web Driver, with params for Chrome or Firefox
# Or in production to run headless
def getBrowser(run_env):
if run_env == 'dev':
print("Chrome is a go!")
br = wd.Chrome()
return br
elif run_env == "def":
print("Firefox go vroom")
br = wd.Firefox()
return br
elif run_env == "prod":
start_cmd = "Xvfb :91 && export DISPLAY=:91 &"
xvfb = Xvfb()
os.system(start_cmd)
xvfb.start()
print("started Xvfb")
br = wd.Firefox()
return br
else:
print("Failed", sys.argv, arg1)
quit()
# Create Dated URL with zero-padded numbers
def createBasicURL(site_url):
month = datetime.now().month
next_month = odt_next_month.month
year = datetime.now().year
if next_month == 1:
next_year = year+1
links = [
site_url + str(month) + "/" + str(year),
site_url + str(next_month) + "/" + str(next_year)
]
else:
links = [
site_url + str(month) + "/" + str(year),
site_url + str(next_month) + "/" + str(year)
]
return links
# Create Dated URL without zero-padded numbers
def createURLNoZero(site_url):
month = datetime.now().month
next_month = odt_next_month.month
year = datetime.now().year
links = [
site_url + str(year) + "/" + str(month),
]
if next_month == "1":
links.append(site_url + str(int(year)+1) + "/" + str(next_month))
else:
links.append(site_url + str(year) + "/" + str(next_month))
return links
# Create Dated URL Link with zero-padding
def createURL(site_url):
month = datetime.now().month
if month < 10:
month = "0" + str(month)
else:
month = str(month)
next_month = odt_next_month.month
if next_month < 10:
next_month = "0" + str(next_month)
else:
next_month = str(next_month)
year = datetime.now().year
links = [
site_url + str(year) + "/" + month,
]
if next_month == "01":
links.append(site_url + str(int(year)+1) + "/" + next_month)
else:
links.append(site_url + str(year) + "/" + next_month)
return links
# Create Dated URL with dashes
def createDashURL(site_url):
month = datetime.now().month
if month < 10:
month = "0" + str(month)
else:
month = str(month)
next_month = odt.month
if next_month < 10:
next_month = "0" + str(next_month)
else:
next_month = str(next_month)
year = datetime.now().year
links = [
site_url + month + "-" + str(year),
site_url + next_month + "-" + str(year)
]
print(links)
return links
# Add Calendar to Event Object (maybe extraneous)
def add_calendar(event, calendar):
# print("Add Calendar", type(event), event, calendar)
if type(event) is tuple:
event = event[0]
cal = Calendar.objects.get(shortcode=calendar)
event.calendar.add(cal)
event.save()
return event
# Add Calendars to Event Object ??
def add_calendars(event, data):
if type(data['calendars']) is not list:
event.calendar.add(data['calendars'])
else:
for cal in data['calendars']:
event.calendar.add(cal)
event.save()
return event
# Create Basic DigiSnaxx Event
def createBasicEvent(event, event_type, venue):
try:
new_event, created = DSEvent.objects.update_or_create(
event_type = event_type,
show_title = event['title'],
show_link = event['link'],
show_date = event['dateStamp'],
scraper = event['scraper'],
venue = venue
)
new_event = add_calendars(new_event, event)
print("\n+new event+")
return new_event, created
except Exception as e:
print("DT Error: ", e)
ppr(event)
return None, None
# Create iCal Event
def createBasiciCalEvent(event, event_type, venue):
new_event, created = DSEvent.objects.update_or_create(
event_type = event_type,
show_title = event['title'][0],
show_link = event['link'],
show_date = datetime.strptime(str(event['dateStamp'][0]), '%Y-%m-%d %H:%M:%S%z %Z'),
scraper = event['scraper'],
venue = venue
)
new_event = add_calendars(new_event, event)
print("Success")
return new_event, created
def createDetailedEvent2(event, event_type, venue, scraper):
new_event, created = DSEvent.objects.update_or_create(
event_type = event_type,
show_title = event["show_title"],
show_link = event["link"],
show_date = event["dateStamp"],
more_details = event["details"],
scraper = event['scraper'],
venue = venue
)
new_event = add_calendars(new_event, event)
print("Success")
return new_event, created
# Create Detailed Event with Details & Guests
# Details in JSON Format
def createDetailedEvent(event, event_type, venue, scraper):
new_event, created = DSEvent.objects.update_or_create(
event_type = event_type,
show_title = event["show_title"],
show_link = event["link"],
show_date = event["dateStamp"],
guests = " ".join(event["guests"]),
more_details = event["details"],
scraper = event['scraper'],
venue = venue
)
new_event = add_calendars(new_event, event)
print("Success")
return new_event, created
# Create iCal event from DF_Online & Medellin
def createCleanIcalEvent(event, scraper, venue, event_type):
new_date = event['eventDate']
new_event = {}
new_event['scraper'] = scraper
new_event['calendars'] = scraper.calendar
new_event['title'] = event['strSummary'],
new_event['date'] = str(new_date),
new_event['dateStamp'] = str(new_date),
new_event['link'] = venue.website
print("New Event")
# ppr(new_event)
createBasiciCalEvent(new_event, event_type, venue)
# Get events from iCal
def getiCalEvents(gcal, scraper, venue, event_type):
events = []
for component in gcal.walk():
event = {}
event['scraper'] = scraper
event['calendars'] = [scraper.calendar]
event['strSummary'] = f"{(component.get('SUMMARY'))}"
event['strDesc'] = component.get('DESCRIPTION')
event['strLocation'] = component.get('LOCATION')
event['dateStart'] = component.get('DTSTART')
event['dateStamp'] = component.get('DTSTAMP')
if event['dateStamp'] is not None:
event['dateStamp'] = event['dateStamp'].dt
if event['dateStart'] is not None:
try:
event['dateStart'] = event['dateStart'].dt
except Exception as e:
print("what? ", e)
if event['strSummary'] != 'None':
event['details'] = {
"description" : event['strDesc'],
"Location" : event['strLocation'],
}
events.append(event)
return events
# Build iCal Events and Send to Create
def buildiCalEvents(events, event_type, scraper, venue):
for event in events:
e = {}
e['calendars'] = event['calendars']
try:
e['dateStamp'] = event['dateStart'][0]
except:
e['dateStamp'] = event['dateStart']
e['title'] = event['strSummary']
e['scraper'] = scraper
e['link'] = venue.website
try:
createBasicEvent(e, event_type, venue)
scraper.items+=1
except Exception as e:
print("Error: ", e)
scraper.save()
return
def getMDEVenue(venue, event):
if venue.name == "DANCEFREE":
venue.website = "https://www.instagram.com/dancefreeco"
if venue.name == "Vintrash":
venue.website = "https://www.instagram.com/vintrashbar"
if venue.name == "The Wandering Paisa":
venue.website = "https://wanderingpaisahostel.com"
if venue.name == "Dulce Posion":
venue.website = "https://www.instagram.com/dulceposionr"
if venue.name == "Blood Dance Company":
venue.website = "https://www.instagram.com/blooddancecompany"
if venue.name == "OLSA Certified Spanish School":
venue.website = "https://www.olsafoundation.org/"
if event['strSummary'] == "Merli Rooftop Language Exchange":
venue.website = "https://calendar.google.com/calendar/embed?src=46ae0446724b1b3ee83cbd7dbc0db6a235bf97509ad860ca91eada3c267b5e41%40group.calendar.google.com&ctz=America%2FBogota"
if "Concious Warrior" in event['strSummary']:
venue.website = "https://www.consciouscolombia.com/"
venue.save()
print(venue)
return
# Get iCal events for Medellin & OnlineEvents
def getiCalRepeateEvents(gcal, scraper, venue, event_type, cal):
for component in gcal.walk():
event = {}
event['scraper'] = scraper
event['calendars'] = [scraper.calendar]
event['strSummary'] = f"{(component.get('SUMMARY'))}"
event['strDesc'] = component.get('DESCRIPTION')
event['strLocation'] = str(component.get('LOCATION'))
event['dateStart'] = component.get('DTSTART')
event['dateStamp'] = component.get('DTSTAMP')
if event['strSummary'] != 'None':
event['details'] = {
"description" : event['strDesc'],
"Location" : event['strLocation'],
}
if event['dateStamp'] != None:
event['dateStart'] = event['dateStart'].dt
event['dateStart'] = datetime.strptime(str(event['dateStart']) + " UTC", '%Y-%m-%d %H:%M:%S%z %Z')
event['timezone'] = str(event['dateStart'])[-6:].strip()
rules = component.get('RRule')
try:
if rules['FREQ'][0] == 'WEEKLY':
date = datetime.today().date() - timedelta(days=datetime.today().weekday())
date = datetime.combine(date, event['dateStart'].time())
days = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]
event = splitLocation(event, city="Medellin")
for day in rules['BYDAY']:
nday = days.index(day)
if cal == 'mde':
getMDEVenue(event['venue'],event)
print(event)
iCalEventRepeatFilter(nday, date, event, scraper, event['venue'], "Ed")
except Exception as e:
print("Error: ", e, "\n\n")
# ppr(event)
pass
def iCalEventRepeatFilter(day, date, event, scraper, venue, event_type):
print("repeate events")
days = [day-1, day+6, day+13]
for day in days:
event['dateStamp'] = date + timedelta(days=day)
dateStart = str(event['dateStamp']) + event['timezone'] + ' UTC'
event['eventDate'] = dateStart
createCleanIcalEvent(event, scraper, venue, event_type)
return
def splitLocation(event, **kwargs):
loc_split = event['strLocation'].split(',')
# ppr(loc_split)
venue_name = loc_split[0]
venue, created = Organization.objects.get_or_create(
name=venue_name,
)
event['venue'] = venue
if kwargs['city']:
venue.city = kwargs['city']
venue.save()
return event
# ARCHIVED Methods
def createBasicArticle(article, event_type, organization):
new_article, created = Promo.objects.update_or_create(
promo_type = 'Ja',
title = article['title'],
target_link = article['link'],
published = True,
organization = organization
)
return new_article, created

View File

@@ -0,0 +1,95 @@
# Generated by Django 6.0.1 on 2026-02-18 19:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Tags',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=31, unique=True)),
('desc', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Company',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=63)),
('unq_entity_id', models.CharField(blank=True, max_length=63, null=True)),
('website', models.URLField(blank=True, max_length=127, null=True)),
('short_desc', models.CharField(blank=True, max_length=63, null=True)),
('long_desc', models.TextField(blank=True, null=True)),
('gmap_link', models.CharField(blank=True, max_length=253, null=True)),
('address_complete', models.CharField(blank=True, max_length=127, null=True)),
('address_numbers', models.CharField(blank=True, max_length=63, null=True)),
('address_type', models.CharField(blank=True, max_length=31, null=True)),
('city', models.CharField(blank=True, max_length=127, null=True)),
('state', models.CharField(blank=True, max_length=127, null=True)),
('zip_code', models.CharField(blank=True, max_length=15, null=True)),
('tags', models.ManyToManyField(blank=True, to='contracts.tags')),
],
options={
'verbose_name_plural': 'Companies',
'ordering': ['name'],
'unique_together': {('name', 'website')},
},
),
migrations.CreateModel(
name='Contract',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=31, unique=True)),
('notice_id', models.CharField(blank=True, max_length=31, null=True)),
('related_notice_id', models.CharField(blank=True, max_length=31, null=True)),
('opp_type', models.CharField(blank=True, max_length=63, null=True)),
('pub_date', models.DateField(blank=True, null=True)),
('us_dept', models.CharField(blank=True, max_length=31, null=True)),
('us_dept_sub_tier', models.CharField(blank=True, max_length=31, null=True)),
('major_dept', models.CharField(blank=True, max_length=31, null=True)),
('us_office', models.CharField(blank=True, max_length=31, null=True)),
('award_date', models.CharField(blank=True, max_length=31, null=True)),
('award_num', models.CharField(blank=True, max_length=31, null=True)),
('unq_entity_id', models.CharField(blank=True, max_length=31, null=True)),
('awarded_name', models.CharField(blank=True, max_length=31, null=True)),
('awarded_addr', models.CharField(blank=True, max_length=31, null=True)),
('contract_value', models.CharField(blank=True, max_length=31, null=True)),
('orig_set_aside', models.CharField(blank=True, max_length=127, null=True)),
('prod_svc_code', models.CharField(blank=True, max_length=127, null=True)),
('naics_code', models.CharField(blank=True, max_length=127, null=True)),
('contract_url', models.CharField(blank=True, max_length=127, null=True)),
('description', models.TextField(blank=True, null=True)),
('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contracts.company')),
],
options={
'verbose_name_plural': 'Contracts',
'ordering': ['pub_date', 'notice_id'],
'unique_together': {('notice_id', 'unq_entity_id')},
},
),
migrations.CreateModel(
name='Exec',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=63)),
('linkedin', models.URLField(blank=True, max_length=127, null=True)),
('short_desc', models.CharField(blank=True, max_length=63, null=True)),
('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contracts.company')),
('tags', models.ManyToManyField(blank=True, to='contracts.tags')),
],
options={
'verbose_name_plural': 'Execs',
'ordering': ['name'],
'unique_together': {('name', 'company')},
},
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 6.0.1 on 2026-02-18 19:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='contract',
name='pub_date_txt',
field=models.CharField(blank=True, max_length=63, null=True),
),
migrations.AlterField(
model_name='contract',
name='pub_date',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-02-18 20:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0002_contract_pub_date_txt_alter_contract_pub_date'),
]
operations = [
migrations.AlterField(
model_name='contract',
name='company',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contracts.company'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-02-18 20:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0003_alter_contract_company'),
]
operations = [
migrations.AlterField(
model_name='contract',
name='notice_id',
field=models.CharField(default='000000000000000', max_length=31),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-02-18 20:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0004_alter_contract_notice_id'),
]
operations = [
migrations.AlterField(
model_name='contract',
name='title',
field=models.CharField(max_length=31),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 6.0.1 on 2026-02-18 23:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0005_alter_contract_title'),
]
operations = [
migrations.AlterModelOptions(
name='contract',
options={'ordering': ['-pub_date', 'notice_id'], 'verbose_name_plural': 'Contracts'},
),
migrations.RemoveField(
model_name='contract',
name='awarded_addr',
),
migrations.RemoveField(
model_name='contract',
name='awarded_name',
),
migrations.AlterField(
model_name='company',
name='unq_entity_id',
field=models.CharField(blank=True, max_length=63, null=True, unique=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-02-18 23:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0006_alter_contract_options_remove_contract_awarded_addr_and_more'),
]
operations = [
migrations.AlterField(
model_name='company',
name='unq_entity_id',
field=models.CharField(max_length=63, unique=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-02-18 23:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0007_alter_company_unq_entity_id'),
]
operations = [
migrations.AlterField(
model_name='contract',
name='award_date',
field=models.DateField(blank=True, max_length=31, null=True),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 6.0.1 on 2026-02-18 23:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0008_alter_contract_award_date'),
]
operations = [
migrations.AlterUniqueTogether(
name='contract',
unique_together={('notice_id', 'unq_entity_id', 'pub_date_txt')},
),
migrations.AlterField(
model_name='contract',
name='title',
field=models.CharField(max_length=254),
),
]

View File

@@ -0,0 +1,45 @@
# Generated by Django 6.0.1 on 2026-02-19 02:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0009_alter_contract_unique_together_alter_contract_title'),
]
operations = [
migrations.CreateModel(
name='OriginalContract',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.CharField(max_length=31, unique=True)),
],
),
migrations.CreateModel(
name='Paragraph',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(blank=True, null=True)),
('link', models.CharField(max_length=255, unique=True)),
('paragraph', models.TextField(blank=True, null=True)),
],
),
migrations.AlterField(
model_name='contract',
name='award_date',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='contract',
name='original_contract_number',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contracts.originalcontract'),
),
migrations.AddField(
model_name='originalcontract',
name='para',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contracts.paragraph'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 6.0.1 on 2026-02-19 02:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0010_originalcontract_paragraph_alter_contract_award_date_and_more'),
]
operations = [
migrations.AlterField(
model_name='paragraph',
name='date',
field=models.DateField(),
),
migrations.AlterField(
model_name='paragraph',
name='link',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='paragraph',
name='paragraph',
field=models.TextField(),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-02-19 02:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0011_alter_paragraph_date_alter_paragraph_link_and_more'),
]
operations = [
migrations.AlterField(
model_name='originalcontract',
name='number',
field=models.CharField(max_length=63),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 6.0.1 on 2026-02-19 02:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('contracts', '0012_alter_originalcontract_number'),
]
operations = [
migrations.AlterUniqueTogether(
name='contract',
unique_together=set(),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 6.0.1 on 2026-02-19 08:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('contracts', '0013_alter_contract_unique_together'),
]
operations = [
migrations.AlterModelOptions(
name='paragraph',
options={'ordering': ['-date'], 'verbose_name_plural': 'Paragraphs'},
),
]

View File

139
contracts/models.py Normal file
View File

@@ -0,0 +1,139 @@
from django.db import models
from django.core.files.storage import FileSystemStorage
from django.contrib.auth.models import User
class Paragraph(models.Model):
date = models.DateField(blank=False, null=False, unique=False)
link = models.CharField(max_length=255, unique=False)
paragraph = models.TextField(blank=False, null=False)
class Meta:
verbose_name_plural = "Paragraphs"
ordering = ['-date']
def __unicode__(self):
return "%s" % self.date
def __str__(self):
return u'%s' % self.date
class OriginalContract(models.Model):
number = models.CharField(max_length=63, unique=False)
para = models.ForeignKey(Paragraph, on_delete=models.CASCADE)
def __unicode__(self):
return "%s" % self.number
def __str__(self):
return u'%s' % self.number
class Tags(models.Model):
name = models.CharField(max_length=31, unique=True)
desc = models.TextField(blank=True, null=True)
def __unicode__(self):
return "%s" % self.name
def __str__(self):
return u'%s' % self.name
class Tags(models.Model):
name = models.CharField(max_length=31, unique=True)
desc = models.TextField(blank=True, null=True)
def __unicode__(self):
return "%s" % self.name
def __str__(self):
return u'%s' % self.name
class Company(models.Model):
name = models.CharField(max_length=63)
unq_entity_id = models.CharField(max_length=63, blank=False, null=False, unique=True)
website = models.URLField(max_length=127, blank=True, null=True)
short_desc = models.CharField(max_length=63, blank=True, null=True)
long_desc = models.TextField(blank=True, null=True)
gmap_link = models.CharField(max_length=253, blank=True, null=True)
tags = models.ManyToManyField(Tags, blank=True)
address_complete = models.CharField(max_length=127, blank=True, null=True)
address_numbers = models.CharField(max_length=63, blank=True, null=True)
address_type = models.CharField(max_length=31, blank=True, null=True)
city = models.CharField(max_length=127, blank=True, null=True)
state = models.CharField(max_length=127, blank=True, null=True)
zip_code = models.CharField(max_length=15, blank=True, null=True)
class Meta:
unique_together = ("name", "website")
verbose_name_plural = "Companies"
ordering = ['name']
def __unicode__(self):
return "%s" % self.name
def __str__(self):
return u'%s' % self.name
class Exec(models.Model):
name = models.CharField(max_length=63)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
linkedin = models.URLField(max_length=127, blank=True, null=True)
short_desc = models.CharField(max_length=63, blank=True, null=True)
tags = models.ManyToManyField(Tags, blank=True)
class Meta:
unique_together = ("name", "company")
verbose_name_plural = "Execs"
ordering = ['name']
def __unicode__(self):
return "%s" % self.name
def __str__(self):
return u'%s' % self.name
class Contract(models.Model):
title = models.CharField(max_length=254, unique=False)
original_contract_number = models.ForeignKey(OriginalContract, on_delete=models.CASCADE, blank=True, null=True)
company = models.ForeignKey(Company, blank=True, null=True, on_delete=models.CASCADE)
notice_id = models.CharField(max_length=31, blank=False, null=False)
related_notice_id = models.CharField(max_length=31, blank=True, null=True)
opp_type = models.CharField(max_length=63, blank=True, null=True)
pub_date = models.DateTimeField(blank=True, null=True)
pub_date_txt = models.CharField(max_length=63, blank=True, null=True)
us_dept = models.CharField(max_length=31, blank=True, null=True)
us_dept_sub_tier = models.CharField(max_length=31, blank=True, null=True)
major_dept = models.CharField(max_length=31, blank=True, null=True)
us_office = models.CharField(max_length=31, blank=True, null=True)
award_date = models.DateField(blank=True, null=True)
award_num = models.CharField(max_length=31, blank=True, null=True)
unq_entity_id = models.CharField(max_length=31, blank=True, null=True)
contract_value = models.CharField(max_length=31, blank=True, null=True)
orig_set_aside = models.CharField(max_length=127, blank=True, null=True)
prod_svc_code = models.CharField(max_length=127, blank=True, null=True)
naics_code = models.CharField(max_length=127, blank=True, null=True)
contract_url = models.CharField(max_length=127, blank=True, null=True)
description = models.TextField(blank=True, null=True)
class Meta:
# unique_together = ("notice_id", "unq_entity_id", "pub_date_txt")
verbose_name_plural = "Contracts"
ordering = ['-pub_date', 'notice_id']
def __unicode__(self):
return "%s" % self.notice_id
def __str__(self):
return u'%s' % self.notice_id

27
contracts/serializers.py Normal file
View File

@@ -0,0 +1,27 @@
from rest_framework import serializers
from .models import *
from django.db import models
from django.contrib.auth.models import User
from rest_framework.permissions import BasePermission
############
## Events ##
############
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
# fields = ('id', 'name', 'website', 'city', 'latitude', 'longitude', 'has_map')
fields = '__all__'
class ContractSerializer(serializers.ModelSerializer):
company = CompanySerializer(many=False)
# target_language = serializers.SerializerMethodField()
class Meta:
model = Contract
fields = '__all__'
depth = 2
# fields = ('id', 'name',)

3
contracts/tests.py Normal file
View File

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

26
contracts/urls.py Normal file
View File

@@ -0,0 +1,26 @@
"""ds_events URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include, re_path
from .views import *
urlpatterns = [
re_path(r'^contracts/', ContractAPIView.as_view(), name="get-contracts-limit"),
re_path(r'^contracts-all/', ContractAllAPIView.as_view(), name="get-contracts"),
re_path(r'^companies/', CompanyAPIView.as_view(), name="get-companies"),
# re_path(r'^events-token/', EventsTokenAPIView.as_view(), name="get-token-events"),
]

64
contracts/views.py Normal file
View File

@@ -0,0 +1,64 @@
from django.shortcuts import render
from datetime import datetime, timedelta
import pytz, random
from .models import *
from .serializers import *
from django.db.models import Q
from django.db.models import Count
from rest_framework import generics
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
# from durin.auth import TokenAuthentication
# from durin.views import APIAccessTokenView
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from rest_framework.response import Response
from rest_framework_api_key.permissions import HasAPIKey
td = timedelta(hours=7)
odt = datetime.now() - td
# Create your views here.
class ContractAPIView(generics.ListAPIView):
serializer_class = ContractSerializer
queryset = Contract.objects.all()[:25]
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ['id',]
permission_classes = [HasAPIKey]
class ContractAllAPIView(generics.ListAPIView):
serializer_class = ContractSerializer
queryset = Contract.objects.all()
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
search_fields = ['notice_id', 'original_contract_number', 'title', 'description', 'company__name', 'us_dept', 'us_dept_sub_tier', 'us_office', 'naics_code', 'prod_svc_code']
filterset_fields = ['id',]
permission_classes = [HasAPIKey]
class CompanyAPIView(generics.ListAPIView):
serializer_class = CompanySerializer
queryset = Company.objects.all()
permission_classes = [HasAPIKey]
# class PromoAPIView(generics.ListAPIView):
# serializer_class = PromoSerializer
# queryset = Promo.objects.filter(published=True)
# filterset_fields = ['organization__name', 'calendar__shortcode',]
# search_fields = ['organization__name', 'calendar__shortcode',]
# # permission_classes = [HasAPIKey]
# def get_queryset(self):
# calendar = self.request.GET.get('calendar__shortcode')
# queryset = Promo.objects.filter(published=True, calendar__shortcode=calendar).order_by('?')
# return queryset

View File

@@ -0,0 +1,46 @@
# Generated by Django 6.0.1 on 2026-02-06 18:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0048_organization_has_map'),
]
operations = [
migrations.AddField(
model_name='organization',
name='city_lnk',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org_city', to='events.place'),
),
migrations.AddField(
model_name='organization',
name='state_lnk',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org_state', to='events.place'),
),
migrations.AlterField(
model_name='organization',
name='org_type',
field=models.CharField(choices=[('Gv', 'Government'), ('Fb', 'Food & Beverage'), ('Re', 'Retail'), ('Se', 'Service'), ('Vn', 'Venue'), ('Ud', 'Undefined')], default='Re', max_length=31),
),
migrations.AlterField(
model_name='place',
name='connection_type',
field=models.CharField(choices=[('Pc', 'Precinct'), ('Mu', 'Municipality'), ('Ci', 'City'), ('Co', 'County'), ('Ld', 'Legislative District'), ('St', 'State')], default='Ci', max_length=31),
),
migrations.CreateModel(
name='Official',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=31, unique=True)),
('website', models.CharField(blank=True, max_length=31, null=True)),
('position', models.CharField(choices=[('Gv', 'Governor'), ('Sc', 'Secretary'), ('Re', 'Representative'), ('Sn', 'Senator'), ('Sr', 'State Rep'), ('Ss', 'State Senator'), ('Cc', 'County Commissioner'), ('Cm', 'Council Member'), ('Ju', 'Judge'), ('Bm', 'Board Member')], default='Ci', max_length=31)),
('notes', models.TextField(blank=True, null=True)),
('boss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.place')),
('employer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.organization')),
],
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 6.0.1 on 2026-02-18 19:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0049_organization_city_lnk_organization_state_lnk_and_more'),
]
operations = [
migrations.AddField(
model_name='official',
name='bluesky',
field=models.CharField(blank=True, max_length=31, null=True),
),
migrations.AddField(
model_name='official',
name='email',
field=models.CharField(blank=True, max_length=31, null=True),
),
migrations.AddField(
model_name='official',
name='instagram',
field=models.CharField(blank=True, max_length=31, null=True),
),
migrations.AddField(
model_name='official',
name='upscroll',
field=models.CharField(blank=True, max_length=31, null=True),
),
migrations.AddField(
model_name='official',
name='youtube',
field=models.CharField(blank=True, max_length=31, null=True),
),
]

View File

@@ -80,7 +80,6 @@ class Place(models.Model):
return u'%s' % self.name return u'%s' % self.name
class Official(models.Model): class Official(models.Model):
POSITION_TYPE = ( POSITION_TYPE = (
('Gv', 'Governor'), ('Gv', 'Governor'),
@@ -96,12 +95,18 @@ class Official(models.Model):
) )
name = models.CharField(max_length=31, unique=True) name = models.CharField(max_length=31, unique=True)
website = models.CharField(max_length=31,blank=True, null=True)
boss = models.ForeignKey("Place", on_delete=models.CASCADE) boss = models.ForeignKey("Place", on_delete=models.CASCADE)
employer = models.ForeignKey("Organization", on_delete=models.CASCADE) employer = models.ForeignKey("Organization", on_delete=models.CASCADE)
position = models.CharField(max_length=31, choices=POSITION_TYPE, default='Ci') position = models.CharField(max_length=31, choices=POSITION_TYPE, default='Ci')
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
website = models.CharField(max_length=31, blank=True, null=True)
email = models.CharField(max_length=31, blank=True, null=True)
bluesky = models.CharField(max_length=31, blank=True, null=True)
instagram = models.CharField(max_length=31, blank=True, null=True)
youtube = models.CharField(max_length=31, blank=True, null=True)
upscroll = models.CharField(max_length=31, blank=True, null=True)
def __unicode__(self): def __unicode__(self):
return "%s" % self.name return "%s" % self.name
@@ -135,8 +140,8 @@ class Organization(models.Model):
barrio = models.CharField(max_length=127, blank=True, null=True) barrio = models.CharField(max_length=127, blank=True, null=True)
city = models.CharField(max_length=127, blank=True, null=True) city = models.CharField(max_length=127, blank=True, null=True)
state = models.CharField(max_length=127, blank=True, null=True) state = models.CharField(max_length=127, blank=True, null=True)
city = models.ForeignKey(Place, on_delete=models.CASCADE, related_name="org_city") city_lnk = models.ForeignKey(Place, on_delete=models.CASCADE, blank=True, null=True, related_name="org_city")
state = models.ForeignKey(Place, on_delete=models.CASCADE, related_name="org_state" ) state_lnk = models.ForeignKey(Place, on_delete=models.CASCADE, blank=True, null=True, related_name="org_state" )
zip_code = models.CharField(max_length=15, blank=True, null=True) zip_code = models.CharField(max_length=15, blank=True, null=True)
phone_number = models.CharField(max_length=255, blank=True, null=True) phone_number = models.CharField(max_length=255, blank=True, null=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB