Change Default Homepage When Adding Wagtail to an Existing Django Project - django

I followed the instructions from the Wagtail site for adding Wagtail to an existing Django project. At the end of the instructions, it said:
Note that there’s one small difference when not using the Wagtail project template: Wagtail creates an initial homepage of the basic type Page, which does not include any content fields beyond the title. You’ll probably want to replace this with your own HomePage class - when you do so, ensure that you set up a site record (under Settings / Sites in the Wagtail admin) to point to the new homepage.
I would like to change the 'basic type Page' to my model class BlogIndex. I know I can add the BlogIndex as a subpage of the default one and then point to it as described in the instructions. Instead, I would like to change the existing page. It bugs me having an unused page at the root. It would be a cleaner approach just to use it.
After adding my new class and migrating it, the django_content_type table looked like this:
1,admin,logentry
2,auth,permission
3,auth,group
4,auth,user
5,contenttypes,contenttype
6,sessions,session
7,wagtailcore,page
8,wagtailadmin,admin
9,wagtaildocs,document
10,wagtailimages,image
11,wagtailforms,formsubmission
12,wagtailredirects,redirect
13,wagtailembeds,embed
14,wagtailusers,userprofile
15,wagtailimages,rendition
16,wagtailimages,uploadedimage
17,wagtailsearch,query
18,wagtailsearch,querydailyhits
19,wagtailcore,grouppagepermission
20,wagtailcore,pagerevision
21,wagtailcore,pageviewrestriction
22,wagtailcore,site
23,wagtailcore,collection
24,wagtailcore,groupcollectionpermission
25,wagtailcore,collectionviewrestriction
26,taggit,tag
27,taggit,taggeditem
28,blog,blogindex
There is another table: wagtailcore_pages that contains three rows:
1,0001,1,1,Root,root,true,false,/,"",false,"",,,false,7
3,000100010001,3,0,Blog Index,blog-index,true,false,/blog/blog-index/,"",false,"",,,false,28
2,00010001,2,1,Welcome to your new Wagtail site!,home,true,false,/blog/,"",false,"",,,false,7
The last column is the content_type_id, which maps to the table above. I tried changing the value from 7 to 28. When I went to access the page in Wagtail admin, I got the error:
DoesNotExist at /cms/pages/2/
BlogIndex matching query does not exist.
Request Method: GET
Request URL: http://localhost:8006/cms/pages/2/
Django Version: 3.0.7
Exception Type: DoesNotExist
Exception Value:
BlogIndex matching query does not exist.
Exception Location: /Users/curt/htdocs/zetcho/venv/lib/python3.8/site-packages/django/db/models/query.py in get, line 415
Python Executable: /Users/curt/htdocs/zetcho/venv/bin/python
Python Version: 3.8.2
This happened before and after I added the the BlogIndex page that you see in the wagtailcore_pages. Both were after I ran the migration to add it to the model. When I change it back to 7, everything is back to normal. My guess is that it's looking for the model in the wrong place.
I then created a standalone Wagtail app to see how it was different. The start of django_content_type table looked like this:
1,wagtailcore,page
2,home,homepage
3,wagtailadmin,admin
The app_label 'home' is the name of the app that the wagtail process created. The home app has a model file with a HomePage class in it.
The wagtailcore_pages for the stand alone app contains two rows:
1,0001,1,1,Root,root,1,0,/,"",0,"",,,0,1
3,00010001,2,0,Home,home,1,0,/home/,"",0,"",,,0,2
It's interesting that the id for the second row is 3 (the first column is id). Given the field is auto increment, a row with the id 2 must have been added and then deleted. The only thing I did after the migration to do runserver to insure it worked.
Clearly, I'm missing another step or two to get it to work assuming that's even possible. Any idea what they might be?
There's a two or three year old ticket on the Wagtail site regarding the setting of the default page, but on solution.
Update
I attempted what I understood to be #gasman's solution. The wagtail_core_page now looks like this:
1,0001,1,1,Root,root,true,false,/,"",false,"",,,false,7
5,000100010001,3,0,Blog Index,blog-index,true,false,/blog/blog-index/,"",false,"",,,false,28
2,00010001,2,1,Welcome to your new Wagtail site!,home,true,false,/blog/,"",false,"",,,false,28
The only change here is setting the page type for the second row to 28. I also changed the blog_blogindex table to this:
2,<p>Introduction</p>
The first column had been five. I've added and deleted a few times, thus the five. I stopped and restarted the application and got the following error when I added /cms to the url:
KeyError at /cms/
5
Request Method: GET
Request URL: http://localhost:8006/cms/
Django Version: 3.0.7
Exception Type: KeyError
Exception Value:
5
Exception Location: /Users/curt/htdocs/zetcho/venv/lib/python3.8/site-packages/wagtail/core/query.py in specific_iterator, line 403
Python Executable: /Users/curt/htdocs/zetcho/venv/bin/python
Python Version: 3.8.2
My guess is because I still have the third row in the wagtail_core table above. I had that row in order to get a row to appear in the blog_blogindex table. I next tried to delete the row with the five in it and got a postgre/SQL error:
[23503] ERROR: update or delete on table "wagtailcore_page" violates foreign key constraint "wagtailcore_pagerevi_page_id_d421cc1d_fk_wagtailco" on table "wagtailcore_pagerevision" Detail: Key (id)=(5) is still referenced from table "wagtailcore_pagerevision".
After realizing I misread #gasman's clearly worded response, I added a second row to blog_blogindex:
5,<p>Introduction</p>
2,<p>unused page</p>
There only needs to be the one entry: the second one. I changed the seven to 28 in the wogtailcore_page for the row created as part of the setup. Wagtail ran without errors and the default page is using the BlogIndex class.
I had an additional problem: the site row was deleted. I'm pretty sure that was a result of me deleting the page to which it was pointing. I had changed the default home page in settings. I just added the row back and I was good. I should have changed the site row back first.
The Fix
Add Wagtail to your project following the Wagtail instructions
Add a model class for the home/base page you wish to use. I set mine up in a separate app.
Run makemigrations and migrate.
You should back up your database at this point in case things go horribly wrong in the next steps. You will be manually changing table data.
Access the database and add the row to the new created class table as described by #gasman.
Change the content_type_id column in wagtailcore_page of the second row to the id of the newly create class. There should only be two rows with the first one being the root. You can find the id in the django_content_type table.
Commit your changes and run your app.

The missing step is to create an entry in the blog_blogindex table with page_ptr_id=2, to accompany the wagtailcore_page record. Wagtail uses multi-table inheritance for Page objects, so the data for a BlogIndex instance is split between wagtailcore_page (which contains the fields common to all Page models) and blog_blogindex (which contains the fields defined on BlogIndex specifically).
However, I'm not aware of any way to create a blog_blogindex entry through the Django ORM as a standalone operation - if you try it, it will attempt to create a new wagtailcore_page entry at the same time. The 'home' app in the standalone project works around this (in the 0002_create_homepage migration) by deleting the old Page instance and creating a HomePage to replace it (creating entries in both wagtailcore_page and home_homepage) - this results in the missing ID 2. If you really wanted to avoid this deletion/recreation step in your own app, you'd presumably have to create the blog_blogindex entry with a raw SQL INSERT query.

Related

Error while working with two databases in Django: sqlite3.IntegrityError: NOT NULL constraint failed: wagtailcore_page.draft_title

I'm working on a Django Project with Wagtail which uses two databases. The first one is the standard sql lite database for all django models (called db_tool.sqlite3), the other one is also sql lite but for a wagtail integration (called db.sqlite3).
I wanted to migrate to the db_tool.sqlite3 with the following command
python manage.py make migrations
python manage.py migrate --database db_tool
but now I get the following error message regarding wagtail, which I never got before.
django.db.utils.IntegrityError: NOT NULL constraint failed: wagtailcore_page.draft_title
First of all: I don't understand this, because I named the db_tool in particular and I wonder, why the wagtail integration raises an error when I try to migrate to db_tool.
Second: I see no particular field at my wagtail-pages called draft_title and I don't have any draft page at the moment.
Third: the error message also relates to a migration file of wagtail that can be found in the side-packages (see below). So maybe this is the root of the error, but I don't understand the correlation to the other error message, because since now it worked fine and I changed nothing exept of some content of my wagtail pages.
File "C:\Users\pubr\.conda\envs\iqps_web\lib\site-packages\wagtail\core\migrations\0001_squashed_0016_change_page_url_path_to_text_field.py", line 23, in initial_data
root = Page.objects.create(
The wagtail version I use here is wagtail 2.15.2 and I haven't updated it since I started the project...
Due to the fact, that my wagtail-database has the name of the default django-database, could it be possible, that I accidentally tried a migration which was ment for the tool_db.sqlite3 without naming it in the migrate-command and caused this error by doing that?
So I would be very grateful if anyone knows, where the error comes from, or at least, what I could try out to fix it...
Kind regards and thank you!
It isn't clear to me if your database is currently broken or not. Hopefully not, but if it is, please take a back up of each before doing anything else.
This does sound like you might have been trying to operate in the wrong database. Do you have DATABASE_ROUTERS configured? I think that might help you prevent code from one app from getting introduced into the wrong database. The example in the Django docs is mostly focused on read replicas but should be adaptable to your situation: https://docs.djangoproject.com/en/4.1/topics/db/multi-db/#an-example
If your databases are in an incorrect state, start by looking at the django_migrations file in each and then carefully pruning the messed up one until you get back to the separation you have been enforcing.

What is the proper way to create draft page programatically in the Wagtail?

Just to save page I do:
parent_page = Page.objects.get(title="parent")
page = Page(title="child", owner=request.user)
parent_page.add_child(instance=page)
page.save() # not sure if this required
What if I need to save Draft Page? This code seems to be working but I am not sure if it's correct:
parent_page = Page.objects.get(title="parent")
page = Page(title="child", live=False, owner=request.user)
parent_page.add_child(instance=page)
page.save_revision()
page.save()
Do I need call page.save() after page.save_revision()?
With this code everything works ok in my case but I have noticed that PageRevision.user_id is empty. How it can affect my code in future?
From wagtail code I see that I can save revision with user parameter:
page.save_revision(user=request.user, log_action=True)
I have saved revisions without user and don't see any side effect. How this field is critical? Do I need to fill user_id field for all existing revisions?
P.S. looks like it would be better to wrap this code in transaction because when it fails due to validation error manage.py fixtree show me error Incorrect numchild value.
You don't need page.save() in either case - parent_page.add_child handles the saving. (This might be what's causing the manage.py fixtree issue - django-treebeard doesn't update in-memory instances after database operations, so fields like numchild may be getting saved with incorrect values.)
The user field on revisions is optional, because changes to pages are not always performed by users - they might be done programmatically, like you are doing. It's just there for logging purposes (e.g. to show in the revision history of a page in the Wagtail admin).

what is no such column: REFERRED.number?

I'm trying to load a fixture and it gives me:
django.db.utils.OperationalError: Problem installing fixtures: no such column: REFERRED.number
Unfortunately, I cannot show the json, because there is all kind of private stuff in there, but I was hoping someone might explain to me what the REFERRED might mean. What kind of error am I looking for?
I made a few migrations and now the DB is out of wack. So the json is slightly off to the DB. There are multiple things called number though. Referred sounds it's some kind of foreignkey error!? Can you give me any hint what to look for?
I had a similar issue and found out what REFERRED was by adding print(query) in django/db/backends/sqlite3/base.py#L297. That showed me all the queries django was running against my sqlite3 database.
In my case, loaddata was not finding the field id (the primary key field that is default in django) for a model in which I had set primary_key=True to one of its fields. Eventhough django was not automatically generating the id field (correct behaviour), loaddata kept looking for the id field. A possible solution would be to add --natural-primary option, but that didn't work for me ATM.
Ref: https://docs.djangoproject.com/en/1.11/topics/serialization/#topics-serialization-natural-keys

Error adding a new field to 1 model in Django 1.7

I am trying to add a new field to a model - normally a simple process. On one model, I get an error (adding the same field to a different model in the same app causes no problems at all).
The field:
mediumlink = models.URLField(max_length=500, null=True)
Although, any field type or name has the same error.
The error, when using makemigrations:
django.db.utils.ProgrammingError: column images_locationimage.mediumlink does not exist
LINE 1: ...."imagelink", "images_locationimage"."thumblink", "images_lo...
^
I'm stumped! Any help would be awesome.
There are certain files that Django has to have in a working state before it can do other things -- particularly, I think, where models and views are defined (not sure exactly and the list is much shorter with Django 1.7). In any case, if you reference your change before the migration is made and applied, you can find yourself in a catch-22 of needing the migration before you can make the migration. Always get your database in order first before you start using your changes. (In your case, commenting out the offending code will let you proceed with the migration, then quickly get back to where you were.)

Why am I getting a Programming error in Django's Python shell?

I am following the Django tutorial for version 1.6 using PostgreSQL on Arch Linux. I'm on part 1 up to this point:
"Give the Poll a couple of Choices. The create call constructs a new
Choice object, does the INSERT statement, adds the choice to the set
of available choices and returns the new Choice object. Django creates
a set to hold the "other side" of a ForeignKey relation
(e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
When I run p.choice_set.all(), instead of an empty list, I get an error in my shell:
ProgrammingError: column "poll_id" of relation "polls_choice" does not exist
This is first that I've seen "choice_set" in the tutorial, and I'm not sure what it does. I tried the whole exercise several times and everything worked right until this point -- I can't figure out what I'm doing wrong.
Drop Poll and Choice tables and run python manage.py syncdb to recreate them according to your current models.py