Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
*.egg
*.egg-info/
dist/
build/
.eggs/

# Virtual environments
.venv/
venv/
ENV/

# Django
db.sqlite3
db.sqlite3-journal
staticfiles/
*.log
local_settings.py

# macOS
.DS_Store

# IDEs
.idea/
.vscode/
*.swp
*.swo

# Data migration artefacts (generated, not committed)
legacy_export.json
321 changes: 321 additions & 0 deletions COMPLETION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
# Phase 0: Scaffolding — COMPLETION

**Agent:** Phase 0 Scaffolding
**Branch:** `modernize/django5`
**Date:** 2026-06-13

---

## What Was Created

### New project structure

```
pythonfiddle_modern/ ← new Django 5.2 project package
__init__.py
settings.py ← fully configured (see below)
urls.py ← minimal (admin only; URLs agent adds the rest)
wsgi.py
asgi.py
manage.py ← replaced with Django 5.2 version
requirements.txt ← updated with modern deps
.gitignore ← added
docs/modernization/
PORTED_MODELS.md ← full model field documentation
DATA_MIGRATION.md ← placeholder + export/import pseudocode
VALIDATION_CHECKLIST.md ← placeholder for validation agent
old_inspectdb.txt ← "No existing DB found" (no legacy DB in repo)
COMPLETION.md ← this file
```

### Files left intact (legacy reference, READ ONLY)

```
__init__.py ← legacy root package marker
context_processors.py ← legacy (not wired into new settings)
files.py ← legacy static file lists (mediasync)
settings.default.py ← legacy Django 1.4 settings (reference only)
urls.py ← legacy root urlconf (reference only)
locale/ ← existing translation files (reusable)
templates/ ← existing templates (starting point for URLs agent)
```

---

## Bootstrap Commands

```bash
# Activate the virtual environment
source /Users/yuguang/Projects/pythonfiddle-modernize/.venv/bin/activate

# Install cloud_ide from source (editable)
pip install -e /Users/yuguang/Projects/django-cloud-ide

# Run migrations
python manage.py migrate

# Start development server
python manage.py runserver 8001
```

The `DJANGO_SETTINGS_MODULE` is pre-configured in `manage.py` as `pythonfiddle_modern.settings`.

---

## cloud_ide Package Install Status

- **Installed:** Yes, editable install from `/Users/yuguang/Projects/django-cloud-ide`
- **Version:** 1.2.0
- **Python version:** 3.14.3
- **Django version:** 5.2.15

### Python 2 → Python 3 / Django 1.4 → Django 5.2 patches applied to cloud_ide

All patches were applied directly to the source at `/Users/yuguang/Projects/django-cloud-ide`:

| File | Change |
|------|--------|
| `cloud_ide/fiddle/compression.py` | Rewrote for Python 3: `cStringIO→io`, added local `compress_string`, fixed exception syntax, fixed `db_type()` to use `connection.settings_dict` |
| `cloud_ide/fiddle/models.py` | Absolute imports; `ugettext_lazy→gettext_lazy`; `@permalink→reverse()`; added `on_delete` to both ForeignKeys; `__unicode__→__str__` |
| `cloud_ide/fiddle/forms.py` | Fixed implicit relative import `from models import *` |
| `cloud_ide/fiddle/admin.py` | Fixed implicit relative import |
| `cloud_ide/fiddle/views.py` | `render_to_response+RequestContext→render`; `simplejson→json`; `is_authenticated()→.is_authenticated`; `is_ajax()→headers check`; `mimetype→content_type`; absolute imports |
| `cloud_ide/fiddle/jsonresponse.py` | Rewrote to extend Django's built-in `JsonResponse` |
| `cloud_ide/fiddle/templatetags/jqtmpl.py` | `TOKEN_BLOCK/TOKEN_VAR→TokenType.BLOCK/VAR`; `TextNode` from `django.template.base` |

**Note:** `cloud_ide/fiddle/migrations/0001_initial.py` was auto-generated by `makemigrations` and lives in the cloud_ide source tree.

---

## Key Models Found in django-cloud-ide

See `docs/modernization/PORTED_MODELS.md` for full details.

**`Language`** (fiddle_language)
- `id` PK, `name` CharField(30)

**`Snippet`** (fiddle_snippet)
- `id`, `title`(80), `slug`(100), `author` FK→User, `description`(300)
- `tags` TaggableManager, `last_modified` auto_now, `code` CompressedTextField, `language` FK→Language

**`CompressedTextField`**: custom TextField that gzip-compresses code blobs in the DB.

---

## Installed Apps (settings.py)

```python
INSTALLED_APPS = [
'whitenoise.runserver_nostatic',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'django.contrib.sitemaps',
'social_django',
'taggit',
'cloud_ide.fiddle',
]
```

---

## Exit Criteria — PASSED

- [x] `python manage.py migrate` exits 0 (all migrations applied including `fiddle.0001_initial`)
- [x] `python manage.py runserver 8001` starts without ImportError on Django 5.2.15

---

---

## Phase 3: Static Assets — COMPLETE

**Agent:** Phase 3 Static Assets
**Date:** 2026-06-13

### Changes Made

| File | Change |
|------|--------|
| `pythonfiddle_modern/settings.py` | Added `STATICFILES_DIRS = [BASE_DIR / 'static']` |
| `static/favicon.ico` | Created minimal 16×16 transparent ICO placeholder |

### What Was Already in Place (from Phase 0)

- `STATIC_ROOT = BASE_DIR / 'staticfiles'` ✓
- Whitenoise `CompressedManifestStaticFilesStorage` in `STORAGES` ✓
- `WhiteNoiseMiddleware` in `MIDDLEWARE` (after `SecurityMiddleware`) ✓
- `whitenoise.runserver_nostatic` in `INSTALLED_APPS` ✓
- `staticfiles/` in `.gitignore` ✓
- No MEDIASYNC or AWS_* settings present ✓

### Verification Results

```
python manage.py collectstatic --noinput
→ 128 static files copied to staticfiles/, 384 post-processed

python manage.py check
→ System check identified no issues (0 silenced)
```

### Exit Criteria — PASSED

- [x] `collectstatic` completes without errors
- [x] `manage.py check` reports 0 issues
- [x] `staticfiles/` excluded from version control via `.gitignore`
- [x] No legacy mediasync/AWS settings in codebase
- [x] Committed and pushed to `modernize/django5`

---

## Open Questions / Handoff Notes

1. **cloud_ide.login** app is not in INSTALLED_APPS. It still imports from the old `social_auth` package. The Auth agent (Phase 2) must either patch it to use `social_django` or replace it entirely.
2. **cloud_ide.shared** and **cloud_ide.snippet** are not in INSTALLED_APPS. The URLs agent (Phase 5) should evaluate whether these are needed.
3. **CLOUD_IDE_TEMPLATES_DIR** is hardcoded to `/Users/yuguang/Projects/cloud-ide-templates`. Move to an env variable before production deployment.
4. **No legacy DB** was found at the legacy project path. If a production DB dump is available, the Data agent should run `inspectdb` against it and update `old_inspectdb.txt`.
5. **django-chunks** was removed. If any templates reference `{% load chunks %}`, those must be updated.
6. **Social auth credentials** are blank in settings.py. The Auth agent must wire up the OAuth keys (via env vars).
7. **Snippet.slug** has no `unique=True` constraint — the Models agent should evaluate adding it.

---

## Phase 1: Models + Data

**Agent:** Phase 1 Models + Data
**Branch:** `modernize/django5` (commit `a77f3e8`)
**Date:** 2026-06-14

### What Was Done

#### Scripts created

| File | Purpose |
|---|---|
| `scripts/export_legacy_data.py` | Reads legacy SQLite DB (no Django needed), dumps Language/Snippet/taggit to JSON, decompresses gzip blobs |
| `scripts/import_legacy_data.py` | Reads JSON export, `get_or_create` Language, `create` Snippet with author fallback, re-applies tags |
| `scripts/README.md` | Full usage docs + roundtrip example |
| `scripts/__init__.py` | Package marker |

#### Documentation updated

- `docs/modernization/DATA_MIGRATION.md` — filled in with actual commands, column-level schema mapping, roundtrip test results, and open issues.

#### Bug fix: `CompressedTextField.get_db_prep_save` (Python 3)

The `get_db_prep_save` method was calling `models.TextField.get_db_prep_save`, which in Python 3 applies `str()` to the compressed bytes — storing `b'\x1f\x8b...'` as TEXT instead of a binary BLOB. Fixed by returning the compressed bytes directly, bypassing TextField's string conversion. Committed to `django-cloud-ide` @ `eb5843d` and pushed to `origin/master`.

### Roundtrip Test — PASSED

```
Export: Languages: 1 Snippets: 1
Import: Snippets created: 1 Snippets skipped: 0
Verify: code='print("hello")' tags=['python','test'] author='testuser' ✓
```

### Open Questions / Handoff Notes

1. **No legacy production DB** was available to test against real data. The roundtrip was validated on a freshly-seeded test DB.
2. **`last_modified` not preserved** — `auto_now=True` resets timestamps on import. Use raw SQL `UPDATE` if original timestamps matter.
3. **`Snippet.slug` uniqueness** — still no `unique=True`. Evaluate before launch to prevent URL collisions.
4. **Placeholder user** (`legacy_import_user`) is auto-created for orphaned snippets. Reassign via Django admin after user migration.
5. **`auth_user` / social auth not migrated** — users must re-authenticate after launch.

---

## Phase 2: Auth — COMPLETE

**Agent:** Phase 2 Auth
**Branch:** `modernize/django5`
**Date:** 2026-06-13

### What Was Done

#### Settings (`pythonfiddle_modern/settings.py`)

| Change | Detail |
|--------|---------|
| `import os` | Added at top of file |
| `SOCIAL_AUTH_URL_NAMESPACE = 'social'` | Must match `namespace=` in `urls.py` |
| OAuth keys via `os.environ.get()` | `GOOGLE_KEY/SECRET`, `TWITTER_KEY/SECRET`, `FACEBOOK_KEY/SECRET` |

Settings already in place from Phase 0 (no changes needed):
- `social_django` in `INSTALLED_APPS` ✓
- `AUTHENTICATION_BACKENDS` with Google/Twitter/Facebook + ModelBackend ✓
- `social_django.context_processors.backends` and `.login_redirect` ✓
- `LOGIN_URL = '/login/'`, `LOGIN_REDIRECT_URL = '/'`, `LOGOUT_REDIRECT_URL = '/'` ✓

#### URLs (`pythonfiddle_modern/urls.py`)

- `path('social-auth/', include('social_django.urls', namespace='social'))` — all OAuth endpoints
- `path('login/', TemplateView.as_view(template_name='login.html'), name='login')` — login page placeholder (Phase 5 will supply the real template)
- `path('logout/', LogoutView.as_view(next_page='/'), name='logout')` — POST-only logout (Django 5 CSRF-safe)

**Note:** Mounted `social_django.urls` only once. Mounting it twice under the same namespace causes `ImproperlyConfigured` in Django 5.

#### cloud_ide.login status — SUPERSEDED (do not add to INSTALLED_APPS)

`/Users/yuguang/Projects/django-cloud-ide/cloud_ide/login/` is legacy Django 1.4 code:
- `models.py` imports from `social_auth.signals` and `social_auth.backends.facebook` (package removed)
- `urls.py` uses `from django.conf.urls.defaults import patterns` (removed in Django 2)
- `views.py` uses `render_to_response` + `RequestContext` (removed in Django 5) and `is_authenticated()` as a method call (fixed upstream in Phase 0)

**Decision:** Do not add `cloud_ide.login` to `INSTALLED_APPS`. `social_django` provides all necessary OAuth login/callback/disconnect views. The `/login/` page (Phase 5) will render provider links using `{% url 'social:begin' 'google-oauth2' %}` etc.

If the `CustomUser` model from `cloud_ide/login/models.py` is needed, port it as a separate `accounts` app that uses `AbstractUser` — but for now the standard `auth.User` is sufficient.

### Verification

```
python manage.py check
→ System check identified no issues (0 silenced)

python manage.py migrate
→ No migrations to apply (social_django migrations were already applied in Phase 0)

reverse('social:begin', kwargs={'backend': 'google-oauth2'}) → /social-auth/login/google-oauth2/
reverse('social:complete', kwargs={'backend': 'google-oauth2'}) → /social-auth/complete/google-oauth2/
reverse('social:begin', kwargs={'backend': 'twitter'}) → /social-auth/login/twitter/
reverse('login') → /login/
reverse('logout') → /logout/
```

### Exit Criteria — PASSED

- [x] `social_django` in `INSTALLED_APPS`, migrations applied
- [x] `AUTHENTICATION_BACKENDS` lists Google OAuth2, Twitter OAuth, Facebook OAuth2
- [x] `SOCIAL_AUTH_URL_NAMESPACE = 'social'` matches URL namespace
- [x] OAuth keys wired to environment variables
- [x] `manage.py check` reports 0 issues
- [x] URL reverse for all social backends works
- [x] `cloud_ide.login` legacy status documented — superseded by `social_django`
- [x] Committed and pushed to `modernize/django5`

### Open Steps — Manual OAuth Credential Setup

Before social login can be tested end-to-end, developers must:

1. **Google OAuth2** — Create a project in [Google Cloud Console](https://console.cloud.google.com/), enable the People API, create OAuth 2.0 credentials, and set the authorized redirect URI to `https://<your-domain>/social-auth/complete/google-oauth2/`. Then set:
```
export GOOGLE_KEY=<client-id>
export GOOGLE_SECRET=<client-secret>
```

2. **Twitter/X OAuth** — Create an app at [developer.twitter.com](https://developer.twitter.com/), enable "Sign in with Twitter", set callback URL to `https://<your-domain>/social-auth/complete/twitter/`. Then set:
```
export TWITTER_KEY=<api-key>
export TWITTER_SECRET=<api-secret>
```

3. **Facebook OAuth2** — Create an app at [developers.facebook.com](https://developers.facebook.com/), add Facebook Login product, set redirect URI to `https://<your-domain>/social-auth/complete/facebook/`. Then set:
```
export FACEBOOK_KEY=<app-id>
export FACEBOOK_SECRET=<app-secret>
```

4. For local development use `http://127.0.0.1:8001` as the domain and register it in each provider's allowed origins.
Loading